Hello community, here is the log from the commit of package python-SQLAlchemy-Utils for openSUSE:Factory checked in at 2020-05-11 13:43:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-SQLAlchemy-Utils (Old) and /work/SRC/openSUSE:Factory/.python-SQLAlchemy-Utils.new.2738 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-SQLAlchemy-Utils" Mon May 11 13:43:51 2020 rev:26 rq:802548 version:0.36.5 Changes: -------- --- /work/SRC/openSUSE:Factory/python-SQLAlchemy-Utils/python-SQLAlchemy-Utils.changes 2020-04-22 20:51:23.407204030 +0200 +++ /work/SRC/openSUSE:Factory/.python-SQLAlchemy-Utils.new.2738/python-SQLAlchemy-Utils.changes 2020-05-11 13:44:02.193461053 +0200 @@ -1,0 +2,17 @@ +Sat May 9 16:12:59 UTC 2020 - Arun Persaud <a...@gmx.de> + +- update to version 0.36.5: + * Added support for dictionary input in CompositeType (#435, pull + request courtesy of cozos) + * Added new EnrichedDateTime and EnrichedDate types (#403, pull + request courtesy of yk-lab) + * Using String instead of LargeBinary for impl of EncryptedType + (#426, pull request courtesy of aicioara) + * Added support for JSONType in EncryptedType (#439, pull request + courtesy of rushilsrivastava) + +- changes from version 0.36.4: + * Added jsonb_sql function (#377, pull request courtesy of getglad) + * Drop py27 support + +------------------------------------------------------------------- Old: ---- SQLAlchemy-Utils-0.36.3.tar.gz New: ---- SQLAlchemy-Utils-0.36.5.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-SQLAlchemy-Utils.spec ++++++ --- /var/tmp/diff_new_pack.Ltnzxv/_old 2020-05-11 13:44:03.449463717 +0200 +++ /var/tmp/diff_new_pack.Ltnzxv/_new 2020-05-11 13:44:03.453463726 +0200 @@ -17,9 +17,9 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} -%bcond_without python2 +%define skip_python2 1 Name: python-SQLAlchemy-Utils -Version: 0.36.3 +Version: 0.36.5 Release: 0 Summary: Various utility functions for SQLAlchemy License: BSD-3-Clause @@ -57,14 +57,6 @@ Recommends: python-passlib >= 1.6 Recommends: python-phonenumbers >= 5.9.2 BuildArch: noarch -%if %{with python2} -BuildRequires: python-enum34 -BuildRequires: python-ipaddr -%endif -%ifpython2 -Requires: python-enum34 -Requires: python-ipaddr -%endif %python_subpackages %description ++++++ SQLAlchemy-Utils-0.36.3.tar.gz -> SQLAlchemy-Utils-0.36.5.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/CHANGES.rst new/SQLAlchemy-Utils-0.36.5/CHANGES.rst --- old/SQLAlchemy-Utils-0.36.3/CHANGES.rst 2020-03-18 10:26:44.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/CHANGES.rst 2020-05-03 20:11:20.000000000 +0200 @@ -4,13 +4,29 @@ Here you can see the full list of changes between each SQLAlchemy-Utils release. -0.36.3 (2019-03-18) +0.36.5 (2020-05-03) +^^^^^^^^^^^^^^^^^^^ + +- Added support for dictionary input in CompositeType (#435, pull request courtesy of cozos) +- Added new EnrichedDateTime and EnrichedDate types (#403, pull request courtesy of yk-lab) +- Using String instead of LargeBinary for impl of EncryptedType (#426, pull request courtesy of aicioara) +- Added support for JSONType in EncryptedType (#439, pull request courtesy of rushilsrivastava) + + +0.36.4 (2020-04-30) +^^^^^^^^^^^^^^^^^^^ + +- Added jsonb_sql function (#377, pull request courtesy of getglad) +- Drop py27 support + + +0.36.3 (2020-03-18) ^^^^^^^^^^^^^^^^^^^ - Added hash method for PhoneNumberType (#428, pull request courtesy of hanc1208) -0.36.2 (2019-03-16) +0.36.2 (2020-03-16) ^^^^^^^^^^^^^^^^^^^ - Added repr for UUIDType (#424, pull request courtesy of ziima) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/PKG-INFO new/SQLAlchemy-Utils-0.36.5/PKG-INFO --- old/SQLAlchemy-Utils-0.36.3/PKG-INFO 2020-03-18 10:27:45.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/PKG-INFO 2020-05-03 20:21:42.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: SQLAlchemy-Utils -Version: 0.36.3 +Version: 0.36.5 Summary: Various utility functions for SQLAlchemy. Home-page: https://github.com/kvesteri/sqlalchemy-utils Author: Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen @@ -18,8 +18,6 @@ Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 @@ -31,6 +29,7 @@ Provides-Extra: anyjson Provides-Extra: babel Provides-Extra: arrow +Provides-Extra: pendulum Provides-Extra: intervals Provides-Extra: phone Provides-Extra: password diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/SQLAlchemy_Utils.egg-info/PKG-INFO new/SQLAlchemy-Utils-0.36.5/SQLAlchemy_Utils.egg-info/PKG-INFO --- old/SQLAlchemy-Utils-0.36.3/SQLAlchemy_Utils.egg-info/PKG-INFO 2020-03-18 10:27:44.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/SQLAlchemy_Utils.egg-info/PKG-INFO 2020-05-03 20:21:42.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: SQLAlchemy-Utils -Version: 0.36.3 +Version: 0.36.5 Summary: Various utility functions for SQLAlchemy. Home-page: https://github.com/kvesteri/sqlalchemy-utils Author: Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen @@ -18,8 +18,6 @@ Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 @@ -31,6 +29,7 @@ Provides-Extra: anyjson Provides-Extra: babel Provides-Extra: arrow +Provides-Extra: pendulum Provides-Extra: intervals Provides-Extra: phone Provides-Extra: password diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/SQLAlchemy_Utils.egg-info/SOURCES.txt new/SQLAlchemy-Utils-0.36.5/SQLAlchemy_Utils.egg-info/SOURCES.txt --- old/SQLAlchemy-Utils-0.36.3/SQLAlchemy_Utils.egg-info/SOURCES.txt 2020-03-18 10:27:44.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/SQLAlchemy_Utils.egg-info/SOURCES.txt 2020-05-03 20:21:42.000000000 +0200 @@ -91,6 +91,12 @@ sqlalchemy_utils/types/encrypted/__init__.py sqlalchemy_utils/types/encrypted/encrypted_type.py sqlalchemy_utils/types/encrypted/padding.py +sqlalchemy_utils/types/enriched_datetime/__init__.py +sqlalchemy_utils/types/enriched_datetime/arrow_datetime.py +sqlalchemy_utils/types/enriched_datetime/enriched_date_type.py +sqlalchemy_utils/types/enriched_datetime/enriched_datetime_type.py +sqlalchemy_utils/types/enriched_datetime/pendulum_date.py +sqlalchemy_utils/types/enriched_datetime/pendulum_datetime.py tests/.DS_Store tests/__init__.py tests/mixins.py @@ -144,6 +150,7 @@ tests/functions/test_identity.py tests/functions/test_is_loaded.py tests/functions/test_json_sql.py +tests/functions/test_jsonb_sql.py tests/functions/test_make_order_by_deterministic.py tests/functions/test_merge_references.py tests/functions/test_naturally_equivalent.py @@ -183,6 +190,9 @@ tests/types/test_datetime_range.py tests/types/test_email.py tests/types/test_encrypted.py +tests/types/test_enriched_date_pendulum.py +tests/types/test_enriched_datetime_arrow.py +tests/types/test_enriched_datetime_pendulum.py tests/types/test_int_range.py tests/types/test_ip_address.py tests/types/test_json.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/SQLAlchemy_Utils.egg-info/requires.txt new/SQLAlchemy-Utils-0.36.5/SQLAlchemy_Utils.egg-info/requires.txt --- old/SQLAlchemy-Utils-0.36.3/SQLAlchemy_Utils.egg-info/requires.txt 2020-03-18 10:27:44.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/SQLAlchemy_Utils.egg-info/requires.txt 2020-05-03 20:21:42.000000000 +0200 @@ -26,6 +26,9 @@ [password] passlib<2.0,>=1.6 +[pendulum] +pendulum>=2.0.5 + [phone] phonenumbers>=5.9.2 @@ -53,6 +56,7 @@ cryptography>=0.6 intervals>=0.7.1 passlib<2.0,>=1.6 +pendulum>=2.0.5 phonenumbers>=5.9.2 pytest>=2.7.1 Pygments>=1.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/docs/generic_relationship.rst new/SQLAlchemy-Utils-0.36.5/docs/generic_relationship.rst --- old/SQLAlchemy-Utils-0.36.3/docs/generic_relationship.rst 2015-08-16 10:02:59.000000000 +0200 +++ new/SQLAlchemy-Utils-0.36.5/docs/generic_relationship.rst 2020-05-03 20:05:20.000000000 +0200 @@ -53,7 +53,7 @@ :: - class Employee(self.Base): + class Employee(Base): __tablename__ = 'employee' id = sa.Column(sa.Integer, primary_key=True) name = sa.Column(sa.String(50)) @@ -74,7 +74,7 @@ 'polymorphic_identity': 'engineer' } - class Activity(self.Base): + class Activity(Base): __tablename__ = 'event' id = sa.Column(sa.Integer, primary_key=True) @@ -116,15 +116,15 @@ :: - class Building(self.Base): + class Building(Base): __tablename__ = 'building' id = sa.Column(sa.Integer, primary_key=True) - class User(self.Base): + class User(Base): __tablename__ = 'user' id = sa.Column(sa.Integer, primary_key=True) - class EventBase(self.Base): + class EventBase(Base): __abstract__ = True object_type = sa.Column(sa.Unicode(255)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/setup.py new/SQLAlchemy-Utils-0.36.5/setup.py --- old/SQLAlchemy-Utils-0.36.3/setup.py 2019-08-20 14:11:55.000000000 +0200 +++ new/SQLAlchemy-Utils-0.36.5/setup.py 2020-05-03 19:55:01.000000000 +0200 @@ -42,6 +42,7 @@ 'anyjson': ['anyjson>=0.3.3'], 'babel': ['Babel>=1.3'], 'arrow': ['arrow>=0.3.4'], + 'pendulum': ['pendulum>=2.0.5'], 'intervals': ['intervals>=0.7.1'], 'phone': ['phonenumbers>=5.9.2'], 'password': ['passlib >= 1.6, < 2.0'], @@ -87,8 +88,6 @@ 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/__init__.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/__init__.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/__init__.py 2020-03-18 10:26:59.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/__init__.py 2020-05-03 19:57:02.000000000 +0200 @@ -36,6 +36,7 @@ identity, is_loaded, json_sql, + jsonb_sql, merge_references, mock_engine, naturally_equivalent, @@ -70,6 +71,8 @@ DateTimeRangeType, EmailType, EncryptedType, + EnrichedDateTimeType, + EnrichedDateType, instrumented_list, InstrumentedList, Int8RangeType, @@ -100,4 +103,4 @@ refresh_materialized_view ) -__version__ = '0.36.3' +__version__ = '0.36.5' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/functions/__init__.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/functions/__init__.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/functions/__init__.py 2019-12-08 18:38:14.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/functions/__init__.py 2020-04-30 09:45:37.000000000 +0200 @@ -6,7 +6,8 @@ has_index, has_unique_index, is_auto_assigned_date_column, - json_sql + json_sql, + jsonb_sql ) from .foreign_keys import ( # noqa dependent_objects, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/functions/database.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/functions/database.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/functions/database.py 2019-12-08 18:38:14.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/functions/database.py 2020-04-30 09:45:37.000000000 +0200 @@ -109,6 +109,78 @@ return value +def jsonb_sql(value, scalars_to_jsonb=True): + """ + Convert python data structures to PostgreSQL specific SQLAlchemy JSONB + constructs. This function is extremly useful if you need to build + PostgreSQL JSONB on python side. + + .. note:: + + This function needs PostgreSQL >= 9.4 + + Scalars are converted to to_jsonb SQLAlchemy function objects + + :: + + jsonb_sql(1) # Equals SQL: to_jsonb(1) + + jsonb_sql('a') # to_jsonb('a') + + + Mappings are converted to jsonb_build_object constructs + + :: + + jsonb_sql({'a': 'c', '2': 5}) # jsonb_build_object('a', 'c', '2', 5) + + + Sequences (other than strings) converted to jsonb_build_array constructs + + :: + + jsonb_sql([1, 2, 3]) # jsonb_build_array(1, 2, 3) + + + You can also nest these data structures + + :: + + jsonb_sql({'a': [1, 2, 3]}) + # jsonb_build_object('a', jsonb_build_array[1, 2, 3]) + + + :param value: + value to be converted to SQLAlchemy PostgreSQL function constructs + :boolean jsonbb: + Flag to alternatively convert the return with a to_jsonb construct + """ + scalar_convert = sa.text + if scalars_to_jsonb: + def scalar_convert(a): + return sa.func.to_jsonb(sa.text(a)) + + if isinstance(value, Mapping): + return sa.func.jsonb_build_object( + *( + jsonb_sql(v, scalars_to_jsonb=False) + for v in itertools.chain(*value.items()) + ) + ) + elif isinstance(value, str): + return scalar_convert("'{0}'".format(value)) + elif isinstance(value, Sequence): + return sa.func.jsonb_build_array( + *( + jsonb_sql(v, scalars_to_jsonb=False) + for v in value + ) + ) + elif isinstance(value, (int, float)): + return scalar_convert(str(value)) + return value + + def has_index(column_or_constraint): """ Return whether or not given column or the columns of given foreign key diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/__init__.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/__init__.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/__init__.py 2019-11-01 11:32:40.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/__init__.py 2020-05-03 19:55:01.000000000 +0200 @@ -9,6 +9,7 @@ from .currency import CurrencyType # noqa from .email import EmailType # noqa from .encrypted.encrypted_type import EncryptedType # noqa +from .enriched_datetime.enriched_date_type import EnrichedDateType # noqa from .ip_address import IPAddressType # noqa from .json import JSONType # noqa from .locale import LocaleType # noqa @@ -39,6 +40,8 @@ from .uuid import UUIDType # noqa from .weekdays import WeekDaysType # noqa +from .enriched_datetime.enriched_datetime_type import EnrichedDateTimeType # noqa isort:skip + class InstrumentedList(_InstrumentedList): """Enhanced version of SQLAlchemy InstrumentedList. Provides some diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/arrow.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/arrow.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/arrow.py 2019-08-20 13:55:26.000000000 +0200 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/arrow.py 2020-05-03 19:55:01.000000000 +0200 @@ -1,17 +1,8 @@ from __future__ import absolute_import -from datetime import datetime - -import six -from sqlalchemy import types - from ..exceptions import ImproperlyConfigured -from .scalar_coercible import ScalarCoercible - -try: - from collections.abc import Iterable -except ImportError: # For python 2.7 support - from collections import Iterable +from .enriched_datetime import ArrowDateTime +from .enriched_datetime.enriched_datetime_type import EnrichedDateTimeType arrow = None try: @@ -20,7 +11,7 @@ pass -class ArrowType(types.TypeDecorator, ScalarCoercible): +class ArrowType(EnrichedDateTimeType): """ ArrowType provides way of saving Arrow_ objects into database. It automatically changes Arrow_ objects to datetime objects on the way in and @@ -58,41 +49,11 @@ # 'an hour ago' """ - impl = types.DateTime - def __init__(self, *args, **kwargs): if not arrow: raise ImproperlyConfigured( "'arrow' package is required to use 'ArrowType'" ) - super(ArrowType, self).__init__(*args, **kwargs) - - def process_bind_param(self, value, dialect): - if value: - utc_val = self._coerce(value).to('UTC') - return utc_val.datetime if self.impl.timezone else utc_val.naive - return value - - def process_result_value(self, value, dialect): - if value: - return arrow.get(value) - return value - - def process_literal_param(self, value, dialect): - return str(value) - - def _coerce(self, value): - if value is None: - return None - elif isinstance(value, six.string_types): - value = arrow.get(value) - elif isinstance(value, Iterable): - value = arrow.get(*value) - elif isinstance(value, datetime): - value = arrow.get(value) - return value - - @property - def python_type(self): - return self.impl.type.python_type + super(ArrowType, self).__init__(datetime_processor=ArrowDateTime, + *args, **kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/encrypted/encrypted_type.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/encrypted/encrypted_type.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/encrypted/encrypted_type.py 2018-04-29 09:09:37.000000000 +0200 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/encrypted/encrypted_type.py 2020-05-03 20:08:22.000000000 +0200 @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- import base64 import datetime +import json import os import six -from sqlalchemy.types import LargeBinary, String, TypeDecorator +from sqlalchemy.types import String, TypeDecorator from sqlalchemy_utils.exceptions import ImproperlyConfigured from sqlalchemy_utils.types.encrypted.padding import PADDING_MECHANISM +from sqlalchemy_utils.types.json import JSONType from sqlalchemy_utils.types.scalar_coercible import ScalarCoercible cryptography = None @@ -110,7 +112,7 @@ encryptor = self.cipher.encryptor() encrypted = encryptor.update(value) + encryptor.finalize() encrypted = base64.b64encode(encrypted) - return encrypted + return encrypted.decode('utf-8') def decrypt(self, value): if isinstance(value, six.text_type): @@ -164,7 +166,7 @@ encrypted = encryptor.update(value) + encryptor.finalize() assert len(encryptor.tag) == self.TAG_SIZE_BYTES encrypted = base64.b64encode(iv + encryptor.tag + encrypted) - return encrypted + return encrypted.decode('utf-8') def decrypt(self, value): if isinstance(value, six.text_type): @@ -208,12 +210,12 @@ value = str(value) value = value.encode() encrypted = self.fernet.encrypt(value) - return encrypted + return encrypted.decode('utf-8') def decrypt(self, value): if isinstance(value, six.text_type): value = str(value) - decrypted = self.fernet.decrypt(value) + decrypted = self.fernet.decrypt(value.encode()) if not isinstance(decrypted, six.string_types): decrypted = decrypted.decode('utf-8') return decrypted @@ -344,7 +346,7 @@ """ - impl = LargeBinary + impl = String def __init__(self, type_in=None, key=None, engine=None, padding=None, **kwargs): @@ -400,6 +402,9 @@ elif issubclass(type_, (datetime.date, datetime.time)): value = value.isoformat() + elif issubclass(type_, JSONType): + value = six.text_type(json.dumps(value)) + return self.engine.encrypt(value) def process_result_value(self, value, dialect): @@ -428,6 +433,9 @@ decrypted_value, type_ ) + elif issubclass(type_, JSONType): + return json.loads(decrypted_value) + # Handle all others return self.underlying_type.python_type(decrypted_value) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/__init__.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/__init__.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/__init__.py 2020-05-03 19:55:01.000000000 +0200 @@ -0,0 +1,4 @@ +# Module for enriched date, datetime type +from .arrow_datetime import ArrowDateTime # noqa +from .pendulum_date import PendulumDate # noqa +from .pendulum_datetime import PendulumDateTime # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/arrow_datetime.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/arrow_datetime.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/arrow_datetime.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/arrow_datetime.py 2020-05-03 19:55:01.000000000 +0200 @@ -0,0 +1,45 @@ +from datetime import datetime + +import six + +from ...exceptions import ImproperlyConfigured + +try: + from collections.abc import Iterable +except ImportError: # For python 2.7 support + from collections import Iterable + +arrow = None +try: + import arrow +except ImportError: + pass + + +class ArrowDateTime(object): + def __init__(self): + if not arrow: + raise ImproperlyConfigured( + "'arrow' package is required to use 'ArrowDateTime'" + ) + + def _coerce(self, impl, value): + if isinstance(value, six.string_types): + value = arrow.get(value) + elif isinstance(value, Iterable): + value = arrow.get(*value) + elif isinstance(value, datetime): + value = arrow.get(value) + return value + + def process_bind_param(self, impl, value, dialect): + if value: + utc_val = self._coerce(impl, value).to('UTC') + return utc_val.datetime\ + if impl.timezone else utc_val.naive + return value + + def process_result_value(self, impl, value, dialect): + if value: + return arrow.get(value) + return value diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/enriched_date_type.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/enriched_date_type.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/enriched_date_type.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/enriched_date_type.py 2020-05-03 19:55:01.000000000 +0200 @@ -0,0 +1,49 @@ +from sqlalchemy import types + +from ..scalar_coercible import ScalarCoercible +from .pendulum_date import PendulumDate + + +class EnrichedDateType(types.TypeDecorator, ScalarCoercible): + """ + Supported for pendulum only. + + Example:: + + + from sqlalchemy_utils import EnrichedDateType + import pendulum + + + class User(Base): + __tablename__ = 'user' + id = sa.Column(sa.Integer, primary_key=True) + birthday = sa.Column(EnrichedDateType(type="pendulum")) + + + user = User() + user.birthday = pendulum.datetime(year=1995, month=7, day=11) + session.add(user) + session.commit() + """ + impl = types.Date + + def __init__(self, date_processor=PendulumDate, *args, **kwargs): + super(EnrichedDateType, self).__init__(*args, **kwargs) + self.date_object = date_processor() + + def _coerce(self, value): + return self.date_object._coerce(self.impl, value) + + def process_bind_param(self, value, dialect): + return self.date_object.process_bind_param(self.impl, value, dialect) + + def process_result_value(self, value, dialect): + return self.date_object.process_result_value(self.impl, value, dialect) + + def process_literal_param(self, value, dialect): + return str(value) + + @property + def python_type(self): + return self.impl.type.python_type diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/enriched_datetime_type.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/enriched_datetime_type.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/enriched_datetime_type.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/enriched_datetime_type.py 2020-05-03 19:55:01.000000000 +0200 @@ -0,0 +1,50 @@ +from sqlalchemy import types + +from ..scalar_coercible import ScalarCoercible +from .pendulum_datetime import PendulumDateTime + + +class EnrichedDateTimeType(types.TypeDecorator, ScalarCoercible): + """ + Supported for arrow and pendulum. + + Example:: + + + from sqlalchemy_utils import EnrichedDateTimeType + import pendulum + + + class User(Base): + __tablename__ = 'user' + id = sa.Column(sa.Integer, primary_key=True) + created_at = sa.Column(EnrichedDateTimeType(type="pendulum")) + # created_at = sa.Column(EnrichedDateTimeType(type="arrow")) + + + user = User() + user.created_at = pendulum.now() + session.add(user) + session.commit() + """ + impl = types.DateTime + + def __init__(self, datetime_processor=PendulumDateTime, *args, **kwargs): + super(EnrichedDateTimeType, self).__init__(*args, **kwargs) + self.dt_object = datetime_processor() + + def _coerce(self, value): + return self.dt_object._coerce(self.impl, value) + + def process_bind_param(self, value, dialect): + return self.dt_object.process_bind_param(self.impl, value, dialect) + + def process_result_value(self, value, dialect): + return self.dt_object.process_result_value(self.impl, value, dialect) + + def process_literal_param(self, value, dialect): + return str(value) + + @property + def python_type(self): + return self.impl.type.python_type diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/pendulum_date.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/pendulum_date.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/pendulum_date.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/pendulum_date.py 2020-05-03 19:55:01.000000000 +0200 @@ -0,0 +1,32 @@ +from ...exceptions import ImproperlyConfigured +from .pendulum_datetime import PendulumDateTime + +pendulum = None +try: + import pendulum +except ImportError: + pass + + +class PendulumDate(PendulumDateTime): + def __init__(self): + if not pendulum: + raise ImproperlyConfigured( + "'pendulum' package is required to use 'PendulumDate'" + ) + + def _coerce(self, impl, value): + if value: + if not isinstance(value, pendulum.Date): + value = super(PendulumDate, self)._coerce(impl, value).date() + return value + + def process_result_value(self, impl, value, dialect): + if value: + return pendulum.parse(value.isoformat()).date() + return value + + def process_bind_param(self, impl, value, dialect): + if value: + return self._coerce(impl, value) + return value diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/pendulum_datetime.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/pendulum_datetime.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/enriched_datetime/pendulum_datetime.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/enriched_datetime/pendulum_datetime.py 2020-05-03 19:55:01.000000000 +0200 @@ -0,0 +1,46 @@ +from datetime import datetime + +import six + +from ...exceptions import ImproperlyConfigured + +pendulum = None +try: + import pendulum +except ImportError: + pass + + +class PendulumDateTime(object): + def __init__(self): + if not pendulum: + raise ImproperlyConfigured( + "'pendulum' package is required to use 'PendulumDateTime'" + ) + + def _coerce(self, impl, value): + if value is not None: + if isinstance(value, pendulum.DateTime): + pass + elif isinstance(value, (int, float)): + value = pendulum.from_timestamp(value) + elif isinstance(value, six.string_types) and value.isdigit(): + value = pendulum.from_timestamp(int(value)) + elif isinstance(value, datetime): + value = pendulum.datetime(value.year, + value.month, value.day, + value.hour, value.minute, + value.second, value.microsecond) + else: + value = pendulum.parse(value) + return value + + def process_bind_param(self, impl, value, dialect): + if value: + return self._coerce(impl, value).in_tz("UTC") + return value + + def process_result_value(self, impl, value, dialect): + if value: + return pendulum.parse(value.isoformat()) + return value diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/pg_composite.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/pg_composite.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/pg_composite.py 2016-12-18 10:08:56.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/pg_composite.py 2020-05-03 19:32:28.000000000 +0200 @@ -49,6 +49,22 @@ ) +Creation +~~~~~~~~ +When creating CompositeType, you can either pass in a tuple or a dictionary. + +:: + account1 = Account() + account1.balance = ('USD', 15) + + account2 = Account() + account2.balance = {'currency': 'USD', 'balance': 15} + + session.add(account1) + session.add(account2) + session.commit() + + Accessing fields ^^^^^^^^^^^^^^^^ @@ -207,16 +223,23 @@ def process(value): if value is None: return None + processed_value = [] for i, column in enumerate(self.columns): + current_value = ( + value.get(column.name) + if isinstance(value, dict) + else value[i] + ) + if isinstance(column.type, TypeDecorator): processed_value.append( column.type.process_bind_param( - value[i], dialect + current_value, dialect ) ) else: - processed_value.append(value[i]) + processed_value.append(current_value) return self.type_cls(*processed_value) return process diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/uuid.py new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/uuid.py --- old/SQLAlchemy-Utils-0.36.3/sqlalchemy_utils/types/uuid.py 2020-03-16 12:56:33.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/sqlalchemy_utils/types/uuid.py 2020-05-03 19:55:01.000000000 +0200 @@ -28,7 +28,7 @@ python_type = uuid.UUID - def __init__(self, binary=True, native=True): + def __init__(self, binary=True, native=True, **kwargs): """ :param binary: Whether to use a BINARY(16) or CHAR(32) fallback. """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/tests/functions/test_jsonb_sql.py new/SQLAlchemy-Utils-0.36.5/tests/functions/test_jsonb_sql.py --- old/SQLAlchemy-Utils-0.36.3/tests/functions/test_jsonb_sql.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/tests/functions/test_jsonb_sql.py 2020-04-30 09:45:37.000000000 +0200 @@ -0,0 +1,32 @@ +import pytest +import sqlalchemy as sa + +from sqlalchemy_utils import jsonb_sql + + +@pytest.mark.usefixtures('postgresql_dsn') +class TestJSONBSQL(object): + + @pytest.mark.parametrize( + ('value', 'result'), + ( + (1, 1), + (14.14, 14.14), + ({'a': 2, 'b': 'c'}, {'a': 2, 'b': 'c'}), + ( + {'a': {'b': 'c'}}, + {'a': {'b': 'c'}} + ), + ({}, {}), + ([1, 2], [1, 2]), + ([], []), + ( + [sa.select([sa.text('1')]).label('alias')], + [1] + ) + ) + ) + def test_compiled_scalars(self, connection, value, result): + assert result == ( + connection.execute(sa.select([jsonb_sql(value)])).fetchone()[0] + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/tests/types/test_arrow.py new/SQLAlchemy-Utils-0.36.5/tests/types/test_arrow.py --- old/SQLAlchemy-Utils-0.36.3/tests/types/test_arrow.py 2019-11-01 11:16:50.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/tests/types/test_arrow.py 2020-04-27 12:45:14.000000000 +0200 @@ -63,7 +63,7 @@ def test_literal_param(self, session, Article): clause = Article.created_at > '2015-01-01' compiled = str(clause.compile(compile_kwargs={"literal_binds": True})) - assert compiled == 'article.created_at > 2015-01-01' + assert compiled == "article.created_at > '2015-01-01'" @pytest.mark.usefixtures('postgresql_dsn') def test_timezone(self, session, Article): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/tests/types/test_composite.py new/SQLAlchemy-Utils-0.36.5/tests/types/test_composite.py --- old/SQLAlchemy-Utils-0.36.3/tests/types/test_composite.py 2019-07-15 14:44:13.000000000 +0200 +++ new/SQLAlchemy-Utils-0.36.5/tests/types/test_composite.py 2020-05-03 19:32:28.000000000 +0200 @@ -66,6 +66,39 @@ assert account.balance.currency == u'ääöö' assert account.balance.amount == 15 + def test_dict_input(self, session, Account): + account = Account( + balance={'currency': 'USD', 'amount': 15} + ) + + session.add(account) + session.commit() + + account = session.query(Account).first() + assert account.balance.currency == 'USD' + assert account.balance.amount == 15 + + def test_incomplete_dict(self, session, Account): + """ + Postgres doesn't allow non-nullabe fields in Composite Types: + + "no constraints (such as NOT NULL) can presently be included" + (https://www.postgresql.org/docs/10/rowtypes.html) + + So this should be allowed. + """ + + account = Account( + balance={'amount': 15} + ) + + session.add(account) + session.commit() + + account = session.query(Account).first() + assert account.balance.currency is None + assert account.balance.amount == 15 + @pytest.mark.skipif('i18n.babel is None') @pytest.mark.usefixtures('postgresql_dsn') @@ -116,6 +149,18 @@ assert account.balance.currency == Currency('USD') assert account.balance.amount == 15 + def test_dict_input(self, session, Account): + account = Account( + balance={'currency': Currency('USD'), 'amount': 15} + ) + + session.add(account) + session.commit() + + account = session.query(Account).first() + assert account.balance.currency == 'USD' + assert account.balance.amount == 15 + @pytest.mark.skipif('i18n.babel is None') @pytest.mark.usefixtures('postgresql_dsn') @@ -154,6 +199,23 @@ ] ) + session.add(account) + session.commit() + + account = session.query(Account).first() + assert account.balances[0].currency == Currency('USD') + assert account.balances[0].amount == 15 + assert account.balances[1].currency == Currency('AUD') + assert account.balances[1].amount == 20 + + def test_dict_input(self, session, type_, Account): + account = Account( + balances=[ + {'currency': Currency('USD'), 'amount': 15}, + {'currency': Currency('AUD'), 'amount': 20} + ] + ) + session.add(account) session.commit() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/tests/types/test_encrypted.py new/SQLAlchemy-Utils-0.36.5/tests/types/test_encrypted.py --- old/SQLAlchemy-Utils-0.36.3/tests/types/test_encrypted.py 2018-11-19 08:53:23.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/tests/types/test_encrypted.py 2020-05-03 20:08:22.000000000 +0200 @@ -4,6 +4,7 @@ import sqlalchemy as sa from sqlalchemy_utils import ColorType, EncryptedType, PhoneNumberType +from sqlalchemy_utils.types import JSONType from sqlalchemy_utils.types.encrypted.encrypted_type import ( AesEngine, AesGcmEngine, @@ -95,6 +96,13 @@ padding_mechanism) ) + json = sa.Column(EncryptedType( + JSONType, + test_key, + encryption_engine, + padding_mechanism) + ) + return User @@ -124,6 +132,11 @@ @pytest.fixture +def user_json(): + return {"key": "value"} + + +@pytest.fixture def user_date(): return date(2010, 10, 2) @@ -170,6 +183,7 @@ user_date, user_time, user_enum, + user_json, user_datetime, test_token, active, @@ -183,6 +197,7 @@ user.date = user_date user.time = user_time user.enum = user_enum + user.json = user_json user.datetime = user_datetime user.access_token = test_token user.is_active = active @@ -278,6 +293,9 @@ def test_enum(self, user, user_enum): assert user.enum == user_enum + def test_json(self, user, user_json): + assert user.json == user_json + def test_lookup_key(self, session, Team): # Add teams self._team_key = 'one' @@ -468,7 +486,7 @@ # 3rd char will be IV. Modify it POS = 3 encrypted = encrypted[:POS] + \ - (b'A' if encrypted[POS] != b'A' else b'B') + \ + ('A' if encrypted[POS] != 'A' else 'B') + \ encrypted[POS + 1:] with pytest.raises(InvalidCiphertextError): self.engine.decrypt(encrypted) @@ -479,7 +497,7 @@ # 19th char will be tag. Modify it POS = 19 encrypted = encrypted[:POS] + \ - (b'A' if encrypted[POS] != b'A' else b'B') + \ + ('A' if encrypted[POS] != 'A' else 'B') + \ encrypted[POS + 1:] with pytest.raises(InvalidCiphertextError): self.engine.decrypt(encrypted) @@ -490,7 +508,7 @@ # 43rd char will be ciphertext. Modify it POS = 43 encrypted = encrypted[:POS] + \ - (b'A' if encrypted[POS] != b'A' else b'B') + \ + ('A' if encrypted[POS] != 'A' else 'B') + \ encrypted[POS + 1:] with pytest.raises(InvalidCiphertextError): self.engine.decrypt(encrypted) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/tests/types/test_enriched_date_pendulum.py new/SQLAlchemy-Utils-0.36.5/tests/types/test_enriched_date_pendulum.py --- old/SQLAlchemy-Utils-0.36.3/tests/types/test_enriched_date_pendulum.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/tests/types/test_enriched_date_pendulum.py 2020-05-03 20:15:41.000000000 +0200 @@ -0,0 +1,68 @@ +from __future__ import unicode_literals + +from datetime import date + +import pytest +import sqlalchemy as sa + +from sqlalchemy_utils.types.enriched_datetime import ( + enriched_date_type, + pendulum_date +) + + +@pytest.fixture +def User(Base): + class User(Base): + __tablename__ = 'users' + id = sa.Column(sa.Integer, primary_key=True) + birthday = sa.Column( + enriched_date_type.EnrichedDateType( + date_processor=pendulum_date.PendulumDate + )) + return User + + +@pytest.fixture +def init_models(User): + pass + + +@pytest.mark.skipif('pendulum_date.pendulum is None') +class TestPendulumDateType(object): + + def test_parameter_processing(self, session, User): + user = User( + birthday=pendulum_date.pendulum.date(1995, 7, 11) + ) + + session.add(user) + session.commit() + + user = session.query(User).first() + assert isinstance(user.birthday, date) + + def test_int_coercion(self, User): + user = User( + birthday=1367900664 + ) + assert user.birthday.year == 2013 + + def test_string_coercion(self, User): + user = User( + birthday='1367900664' + ) + assert user.birthday.year == 2013 + + def test_utc(self, session, User): + time = pendulum_date.pendulum.now("UTC") + user = User(birthday=time) + session.add(user) + assert user.birthday == time + session.commit() + assert user.birthday == time.date() + + def test_literal_param(self, session, User): + clause = User.birthday > '2015-01-01' + compiled = str(clause.compile(compile_kwargs={"literal_binds": True})) + assert compiled == "users.birthday > '2015-01-01'" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/tests/types/test_enriched_datetime_arrow.py new/SQLAlchemy-Utils-0.36.5/tests/types/test_enriched_datetime_arrow.py --- old/SQLAlchemy-Utils-0.36.3/tests/types/test_enriched_datetime_arrow.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/tests/types/test_enriched_datetime_arrow.py 2020-05-03 20:15:55.000000000 +0200 @@ -0,0 +1,91 @@ +from datetime import datetime + +import pytest +import sqlalchemy as sa +from dateutil import tz + +from sqlalchemy_utils.types.enriched_datetime import ( + arrow_datetime, + enriched_datetime_type +) + + +@pytest.fixture +def Article(Base): + class Article(Base): + __tablename__ = 'article' + id = sa.Column(sa.Integer, primary_key=True) + created_at = sa.Column( + enriched_datetime_type.EnrichedDateTimeType( + datetime_processor=arrow_datetime.ArrowDateTime + )) + published_at = sa.Column( + enriched_datetime_type.EnrichedDateTimeType( + datetime_processor=arrow_datetime.ArrowDateTime, + timezone=True + )) + published_at_dt = sa.Column(sa.DateTime(timezone=True)) + return Article + + +@pytest.fixture +def init_models(Article): + pass + + +@pytest.mark.skipif('arrow_datetime.arrow is None') +class TestArrowDateTimeType(object): + + def test_parameter_processing(self, session, Article): + article = Article( + created_at=arrow_datetime.arrow.get(datetime(2000, 11, 1)) + ) + + session.add(article) + session.commit() + + article = session.query(Article).first() + assert article.created_at.datetime + + def test_string_coercion(self, Article): + article = Article( + created_at='2013-01-01' + ) + assert article.created_at.year == 2013 + + def test_utc(self, session, Article): + time = arrow_datetime.arrow.utcnow() + article = Article(created_at=time) + session.add(article) + assert article.created_at == time + session.commit() + assert article.created_at == time + + def test_other_tz(self, session, Article): + time = arrow_datetime.arrow.utcnow() + local = time.to('US/Pacific') + article = Article(created_at=local) + session.add(article) + assert article.created_at == time == local + session.commit() + assert article.created_at == time + + def test_literal_param(self, session, Article): + clause = Article.created_at > '2015-01-01' + compiled = str(clause.compile(compile_kwargs={"literal_binds": True})) + assert compiled == "article.created_at > '2015-01-01'" + + @pytest.mark.usefixtures('postgresql_dsn') + def test_timezone(self, session, Article): + timezone = tz.gettz('Europe/Stockholm') + dt = arrow_datetime.arrow.get(datetime(2015, 1, 1, 15, 30, 45), + timezone) + article = Article(published_at=dt, published_at_dt=dt.datetime) + + session.add(article) + session.commit() + session.expunge_all() + + item = session.query(Article).one() + assert item.published_at.datetime == item.published_at_dt + assert item.published_at.to(timezone) == dt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.36.3/tests/types/test_enriched_datetime_pendulum.py new/SQLAlchemy-Utils-0.36.5/tests/types/test_enriched_datetime_pendulum.py --- old/SQLAlchemy-Utils-0.36.3/tests/types/test_enriched_datetime_pendulum.py 1970-01-01 01:00:00.000000000 +0100 +++ new/SQLAlchemy-Utils-0.36.5/tests/types/test_enriched_datetime_pendulum.py 2020-05-03 20:16:09.000000000 +0200 @@ -0,0 +1,83 @@ +from __future__ import unicode_literals + +from datetime import datetime + +import pytest +import sqlalchemy as sa + +from sqlalchemy_utils.types.enriched_datetime import ( + enriched_datetime_type, + pendulum_datetime +) + + +@pytest.fixture +def User(Base): + class User(Base): + __tablename__ = 'users' + id = sa.Column(sa.Integer, primary_key=True) + created_at = sa.Column( + enriched_datetime_type.EnrichedDateTimeType( + datetime_processor=pendulum_datetime.PendulumDateTime, + )) + return User + + +@pytest.fixture +def init_models(User): + pass + + +@pytest.mark.skipif('pendulum_datetime.pendulum is None') +class TestPendulumDateTimeType(object): + + def test_parameter_processing(self, session, User): + user = User( + created_at=pendulum_datetime.pendulum.datetime(1995, 7, 11) + ) + + session.add(user) + session.commit() + + user = session.query(User).first() + assert isinstance(user.created_at, datetime) + + def test_int_coercion(self, User): + user = User( + created_at=1367900664 + ) + assert user.created_at.year == 2013 + + def test_float_coercion(self, User): + user = User( + created_at=1367900664.0 + ) + assert user.created_at.year == 2013 + + def test_string_coercion(self, User): + user = User( + created_at='1367900664' + ) + assert user.created_at.year == 2013 + + def test_utc(self, session, User): + time = pendulum_datetime.pendulum.now("UTC") + user = User(created_at=time) + session.add(user) + assert user.created_at == time + session.commit() + assert user.created_at == time + + def test_other_tz(self, session, User): + time = pendulum_datetime.pendulum.now("UTC") + local = time.in_tz('Asia/Tokyo') + user = User(created_at=local) + session.add(user) + assert user.created_at == time == local + session.commit() + assert user.created_at == time + + def test_literal_param(self, session, User): + clause = User.created_at > '2015-01-01' + compiled = str(clause.compile(compile_kwargs={"literal_binds": True})) + assert compiled == "users.created_at > '2015-01-01'"