Hello community, here is the log from the commit of package python-SQLAlchemy-Utils for openSUSE:Factory checked in at 2017-09-05 15:14:46 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-SQLAlchemy-Utils (Old) and /work/SRC/openSUSE:Factory/.python-SQLAlchemy-Utils.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-SQLAlchemy-Utils" Tue Sep 5 15:14:46 2017 rev:3 rq:518322 version:0.32.14 Changes: -------- --- /work/SRC/openSUSE:Factory/python-SQLAlchemy-Utils/python-SQLAlchemy-Utils.changes 2016-11-24 21:22:41.000000000 +0100 +++ /work/SRC/openSUSE:Factory/.python-SQLAlchemy-Utils.new/python-SQLAlchemy-Utils.changes 2017-09-05 15:14:46.644971447 +0200 @@ -1,0 +2,14 @@ +Wed Aug 23 05:02:53 UTC 2017 - tbechtold@suse.com + +- update to 0.32.14: + - Fixed drop_database version comparison + - Fixed a DeprecationWarning by using LargeBinary instead of Binary + - Added generic_repr decorator + - TimeZoneType support for static timezones + - Added SQLite support for PasswordType + - Added PhoneNumber as the python_type for PhoneNumberType + - Made auto_delete_orphans support backref tuples + - Added support for multi-column observers +- convert to singlespec + +------------------------------------------------------------------- Old: ---- SQLAlchemy-Utils-0.32.9.tar.gz New: ---- SQLAlchemy-Utils-0.32.14.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-SQLAlchemy-Utils.spec ++++++ --- /var/tmp/diff_new_pack.xekbdk/_old 2017-09-05 15:14:47.284881499 +0200 +++ /var/tmp/diff_new_pack.xekbdk/_new 2017-09-05 15:14:47.288880936 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-SQLAlchemy-Utils # -# Copyright (c) 2016 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,24 +16,24 @@ # +%{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-SQLAlchemy-Utils -Version: 0.32.9 +Version: 0.32.14 Release: 0 Summary: Various utility functions for SQLAlchemy License: BSD-3-Clause Group: Development/Languages/Python Url: https://github.com/kvesteri/sqlalchemy-utils -Source: https://pypi.io/packages/source/S/SQLAlchemy-Utils/SQLAlchemy-Utils-%{version}.tar.gz -BuildRequires: python-SQLAlchemy -BuildRequires: python-devel -BuildRequires: python-setuptools +Source: https://files.pythonhosted.org/packages/source/S/SQLAlchemy-Utils/SQLAlchemy-Utils-%{version}.tar.gz +BuildRequires: %{python_module SQLAlchemy} +BuildRequires: %{python_module devel} +BuildRequires: %{python_module setuptools} +BuildRequires: python-rpm-macros Requires: python-SQLAlchemy BuildRoot: %{_tmppath}/%{name}-%{version}-build -%if 0%{?suse_version} && 0%{?suse_version} <= 1110 -%{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} -%else BuildArch: noarch -%endif + +%python_subpackages %description Various utility functions and custom data types for SQLAlchemy. @@ -42,12 +42,12 @@ %setup -q -n SQLAlchemy-Utils-%{version} %build -python setup.py build +%python_build %install -python setup.py install --prefix=%{_prefix} --root=%{buildroot} +%python_install -%files +%files %{python_files} %defattr(-,root,root,-) %doc LICENSE README.rst %{python_sitelib}/* ++++++ SQLAlchemy-Utils-0.32.9.tar.gz -> SQLAlchemy-Utils-0.32.14.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/CHANGES.rst new/SQLAlchemy-Utils-0.32.14/CHANGES.rst --- old/SQLAlchemy-Utils-0.32.9/CHANGES.rst 2016-07-17 22:00:14.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/CHANGES.rst 2017-03-27 15:48:21.000000000 +0200 @@ -4,6 +4,38 @@ Here you can see the full list of changes between each SQLAlchemy-Utils release. +0.32.14 (2017-03-27) +^^^^^^^^^^^^^^^^^^^^ + +- Fixed drop_database version comparison + + +0.32.13 (2017-03-12) +^^^^^^^^^^^^^^^^^^^^ + +- Fixed a DeprecationWarning by using LargeBinary instead of Binary (#263, pull request courtesy of jacquerie) + + +0.32.12 (2016-12-18) +^^^^^^^^^^^^^^^^^^^^ + +- Added generic_repr decorator + + +0.32.11 (2016-11-19) +^^^^^^^^^^^^^^^^^^^^ + +- TimeZoneType support for static timezones (#244, pull request courtesy of fuhrysteve) +- Added SQLite support for PasswordType (#254, pull request courtesy of frol) + + +0.32.10 (2016-10-20) +^^^^^^^^^^^^^^^^^^^^ + +- Added PhoneNumber as the python_type for PhoneNumberType (#248) +- Made auto_delete_orphans support backref tuples (#234, pull request courtesy of vToMy) + + 0.32.9 (2016-07-17) ^^^^^^^^^^^^^^^^^^^ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/PKG-INFO new/SQLAlchemy-Utils-0.32.14/PKG-INFO --- old/SQLAlchemy-Utils-0.32.9/PKG-INFO 2016-07-17 22:02:35.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/PKG-INFO 2017-03-27 15:57:06.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: SQLAlchemy-Utils -Version: 0.32.9 +Version: 0.32.14 Summary: Various utility functions for SQLAlchemy. Home-page: https://github.com/kvesteri/sqlalchemy-utils Author: Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen @@ -24,5 +24,6 @@ Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/SQLAlchemy_Utils.egg-info/PKG-INFO new/SQLAlchemy-Utils-0.32.14/SQLAlchemy_Utils.egg-info/PKG-INFO --- old/SQLAlchemy-Utils-0.32.9/SQLAlchemy_Utils.egg-info/PKG-INFO 2016-07-17 22:02:34.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/SQLAlchemy_Utils.egg-info/PKG-INFO 2017-03-27 15:57:05.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: SQLAlchemy-Utils -Version: 0.32.9 +Version: 0.32.14 Summary: Various utility functions for SQLAlchemy. Home-page: https://github.com/kvesteri/sqlalchemy-utils Author: Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen @@ -24,5 +24,6 @@ Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content Classifier: Topic :: Software Development :: Libraries :: Python Modules diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/SQLAlchemy_Utils.egg-info/requires.txt new/SQLAlchemy-Utils-0.32.14/SQLAlchemy_Utils.egg-info/requires.txt --- old/SQLAlchemy-Utils-0.32.9/SQLAlchemy_Utils.egg-info/requires.txt 2016-07-17 22:02:34.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/SQLAlchemy_Utils.egg-info/requires.txt 2017-03-27 15:57:05.000000000 +0200 @@ -17,11 +17,13 @@ cryptography>=0.6 [enum] +enum34 [intervals] intervals>=0.7.1 [ipaddress] +ipaddr [password] passlib >= 1.6, < 2.0 @@ -49,7 +51,9 @@ Babel>=1.3 colour>=0.0.4 cryptography>=0.6 +enum34 intervals>=0.7.1 +ipaddr passlib >= 1.6, < 2.0 phonenumbers>=5.9.2 pytest>=2.7.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/docs/data_types.rst new/SQLAlchemy-Utils-0.32.14/docs/data_types.rst --- old/SQLAlchemy-Utils-0.32.9/docs/data_types.rst 2016-04-25 20:10:56.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/docs/data_types.rst 2016-10-20 08:55:26.000000000 +0200 @@ -63,6 +63,14 @@ .. autoclass:: Currency +EmailType +--------- + +.. automodule:: sqlalchemy_utils.types.email + +.. autoclass:: EmailType + + EncryptedType ------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/docs/models.rst new/SQLAlchemy-Utils-0.32.14/docs/models.rst --- old/SQLAlchemy-Utils-0.32.9/docs/models.rst 2015-08-16 10:02:59.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/docs/models.rst 2016-12-18 10:08:56.000000000 +0100 @@ -8,3 +8,11 @@ .. module:: sqlalchemy_utils.models .. autoclass:: Timestamp + + +generic_repr +------------ + +.. module:: sqlalchemy_utils.models + +.. autofunction:: generic_repr diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/setup.cfg new/SQLAlchemy-Utils-0.32.14/setup.cfg --- old/SQLAlchemy-Utils-0.32.9/setup.cfg 2016-07-17 22:02:35.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/setup.cfg 2017-03-27 15:57:06.000000000 +0200 @@ -1,5 +1,5 @@ [egg_info] -tag_date = 0 tag_build = +tag_date = 0 tag_svn_revision = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/setup.py new/SQLAlchemy-Utils-0.32.14/setup.py --- old/SQLAlchemy-Utils-0.32.9/setup.py 2016-04-20 14:29:57.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/setup.py 2017-03-27 15:50:45.000000000 +0200 @@ -91,6 +91,7 @@ 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Software Development :: Libraries :: Python Modules' ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/__init__.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/__init__.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/__init__.py 2016-07-17 22:00:12.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/__init__.py 2017-03-27 15:48:47.000000000 +0200 @@ -53,7 +53,7 @@ force_auto_coercion, force_instant_defaults ) -from .models import Timestamp # noqa +from .models import generic_repr, Timestamp # noqa from .observer import observes # noqa from .primitives import Country, Currency, Ltree, WeekDay, WeekDays # noqa from .proxy_dict import proxy_dict, ProxyDict # noqa @@ -95,4 +95,4 @@ WeekDaysType ) -__version__ = '0.32.9' +__version__ = '0.32.14' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/functions/database.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/functions/database.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/functions/database.py 2016-04-25 20:10:50.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/functions/database.py 2017-03-27 15:46:49.000000000 +0200 @@ -585,17 +585,14 @@ elif engine.dialect.name == 'postgresql' and engine.driver == 'psycopg2': from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT - engine.raw_connection().set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) + + connection = engine.connect() + connection.connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) # Disconnect all users from the database we are dropping. - version = list( - map( - int, - engine.execute('SHOW server_version').first()[0].split('.') - ) - ) + version = connection.dialect.server_version_info pid_column = ( - 'pid' if (version[0] >= 9 and version[1] >= 2) else 'procpid' + 'pid' if (version >= (9, 2)) else 'procpid' ) text = ''' SELECT pg_terminate_backend(pg_stat_activity.%(pid_column)s) @@ -603,12 +600,11 @@ WHERE pg_stat_activity.datname = '%(database)s' AND %(pid_column)s <> pg_backend_pid(); ''' % {'pid_column': pid_column, 'database': database} - engine.execute(text) + connection.execute(text) # Drop the database. - text = 'DROP DATABASE {0}'.format(quote(engine, database)) - engine.execute(text) - + text = 'DROP DATABASE {0}'.format(quote(connection, database)) + connection.execute(text) else: text = 'DROP DATABASE {0}'.format(quote(engine, database)) engine.execute(text) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/functions/sort_query.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/functions/sort_query.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/functions/sort_query.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/functions/sort_query.py 2017-03-27 15:49:24.000000000 +0200 @@ -100,7 +100,7 @@ query = sort_query(query, 'name') - 2. Appying descending sort + 2. Applying descending sort :: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/listeners.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/listeners.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/listeners.py 2016-05-09 12:53:42.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/listeners.py 2016-10-20 09:13:31.000000000 +0200 @@ -235,6 +235,8 @@ 'The relationship argument given for auto_delete_orphans needs to ' 'have a backref relationship set.' ) + if isinstance(backref, tuple): + backref = backref[0] @sa.event.listens_for(sa.orm.Session, 'after_flush') def delete_orphan_listener(session, ctx): @@ -257,7 +259,7 @@ ( session.query(target_class) .filter( - ~getattr(target_class, attr.property.backref).any() + ~getattr(target_class, backref).any() ) .delete(synchronize_session=False) ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/models.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/models.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/models.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/models.py 2016-12-18 10:08:56.000000000 +0100 @@ -1,6 +1,7 @@ from datetime import datetime import sqlalchemy as sa +from sqlalchemy.util.langhelpers import symbol class Timestamp(object): @@ -31,3 +32,67 @@ # When a model with a timestamp is updated; force update the updated # timestamp. target.updated = datetime.utcnow() + + +NO_VALUE = symbol('NO_VALUE') +NOT_LOADED_REPR = '<not loaded>' + + +def _generic_repr_method(self, fields): + state = sa.inspect(self) + field_reprs = [] + if not fields: + fields = state.mapper.columns.keys() + for key in fields: + value = state.attrs[key].loaded_value + if value == NO_VALUE: + value = NOT_LOADED_REPR + else: + value = repr(value) + field_reprs.append('='.join((key, value))) + + return '%s(%s)' % (self.__class__.__name__, ', '.join(field_reprs)) + + +def generic_repr(*fields): + """Adds generic ``__repr__()`` method to a decalrative SQLAlchemy model. + + In case if some fields are not loaded from a database, it doesn't + force their loading and instead repesents them as ``<not loaded>``. + + In addition, user can provide field names as arguments to the decorator + to specify what fields should present in the string representation + and in what order. + + Example:: + + + import sqlalchemy as sa + from sqlalchemy_utils import generic_repr + + + @generic_repr + class MyModel(Base): + __tablename__ = 'mymodel' + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.String) + category = sa.Column(sa.String) + + session.add(MyModel(name='Foo', category='Bar')) + session.commit() + foo = session.query(MyModel).options(sa.orm.defer('category')).one(s) + + assert repr(foo) == 'MyModel(id=1, name='Foo', category=<not loaded>)' + """ + if len(fields) == 1 and callable(fields[0]): + target = fields[0] + target.__repr__ = lambda self: _generic_repr_method(self, fields=None) + return target + else: + def decorator(cls): + cls.__repr__ = lambda self: _generic_repr_method( + self, + fields=fields + ) + return cls + return decorator diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/observer.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/observer.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/observer.py 2016-07-17 22:00:22.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/observer.py 2016-12-18 10:08:56.000000000 +0100 @@ -152,7 +152,7 @@ Observing multiple columns ----------------------- -You can also observe multiple columns by spesifying all the observable columns +You can also observe multiple columns by specifying all the observable columns in the decorator. @@ -317,6 +317,7 @@ for callback, objs in callback_objs.items(): callback(root_obj, *[objs[i] for i in range(len(objs))]) + observer = PropertyObserver() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/email.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/email.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/email.py 2016-07-10 11:03:30.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/email.py 2016-10-20 08:55:26.000000000 +0200 @@ -4,6 +4,33 @@ class EmailType(sa.types.TypeDecorator): + """ + Provides a way for storing emails in a lower case. + + Example:: + + + from sqlalchemy_utils import EmailType + + + class User(Base): + __tablename__ = 'user' + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.Unicode(255)) + email = sa.Column(EmailType) + + + user = User() + user.email = 'John.Smith@foo.com' + user.name = 'John Smith' + session.add(user) + session.commit() + # Notice - email in filter() is lowercase. + user = (session.query(User) + .filter(User.email == 'john.smith@foo.com') + .one()) + assert user.name == 'John Smith' + """ impl = sa.Unicode comparator_factory = CaseInsensitiveComparator diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/encrypted.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/encrypted.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/encrypted.py 2016-07-10 11:03:30.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/encrypted.py 2017-03-12 11:08:09.000000000 +0100 @@ -3,7 +3,7 @@ import datetime import six -from sqlalchemy.types import Binary, String, TypeDecorator +from sqlalchemy.types import LargeBinary, String, TypeDecorator from ..exceptions import ImproperlyConfigured from .scalar_coercible import ScalarCoercible @@ -199,7 +199,7 @@ """ - impl = Binary + impl = LargeBinary def __init__(self, type_in=None, key=None, engine=None, **kwargs): """Initialization.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/password.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/password.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/password.py 2016-07-10 11:03:30.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/password.py 2016-11-19 09:37:07.000000000 +0100 @@ -2,7 +2,7 @@ import six from sqlalchemy import types -from sqlalchemy.dialects import oracle, postgresql +from sqlalchemy.dialects import oracle, postgresql, sqlite from sqlalchemy.ext.mutable import Mutable from ..exceptions import ImproperlyConfigured @@ -80,7 +80,7 @@ """ PasswordType hashes passwords as they come into the database and allows verifying them using a Pythonic interface. This Pythonic interface - relies on setting up automatic data type coercison using the + relies on setting up automatic data type coercion using the :func:`~sqlalchemy_utils.listeners.force_auto_coercion` function. All keyword arguments (aside from max_length) are forwarded to the @@ -192,14 +192,15 @@ if dialect.name == 'postgresql': # Use a BYTEA type for postgresql. impl = postgresql.BYTEA(self.length) - return dialect.type_descriptor(impl) - if dialect.name == 'oracle': + elif dialect.name == 'oracle': # Use a RAW type for oracle. impl = oracle.RAW(self.length) - return dialect.type_descriptor(impl) - - # Use a VARBINARY for all other dialects. - impl = types.VARBINARY(self.length) + elif dialect.name == 'sqlite': + # Use a BLOB type for sqlite + impl = sqlite.BLOB(self.length) + else: + # Use a VARBINARY for all other dialects. + impl = types.VARBINARY(self.length) return dialect.type_descriptor(impl) def process_bind_param(self, value, dialect): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/pg_composite.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/pg_composite.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/pg_composite.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/pg_composite.py 2016-12-18 10:08:56.000000000 +0100 @@ -32,7 +32,7 @@ from collections import OrderedDict import sqlalchemy as sa - from sqlalchemy_utils import Composite, CurrencyType + from sqlalchemy_utils import CompositeType, CurrencyType class Account(Base): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/phone_number.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/phone_number.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/phone_number.py 2016-05-20 08:58:31.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/phone_number.py 2016-10-20 08:50:43.000000000 +0200 @@ -153,9 +153,7 @@ """ STORE_FORMAT = 'e164' impl = types.Unicode(20) - - def python_type(self, text): - return self._coerce(text) + python_type = PhoneNumber def __init__(self, region='US', max_length=20, *args, **kwargs): # Bail if phonenumbers is not found. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/timezone.py new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/timezone.py --- old/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/timezone.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/sqlalchemy_utils/types/timezone.py 2016-11-19 09:35:59.000000000 +0100 @@ -52,9 +52,10 @@ elif backend == 'pytz': try: - from pytz import tzfile, timezone + from pytz import timezone + from pytz.tzinfo import BaseTzInfo - self.python_type = tzfile.DstTzInfo + self.python_type = BaseTzInfo self._to = timezone self._from = six.text_type @@ -71,13 +72,11 @@ ) def _coerce(self, value): - if value and not isinstance(value, self.python_type): + if value is not None and not isinstance(value, self.python_type): obj = self._to(value) if obj is None: raise ValueError("unknown time zone '%s'" % value) - return obj - return value def process_bind_param(self, value, dialect): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/functions/test_make_order_by_deterministic.py new/SQLAlchemy-Utils-0.32.14/tests/functions/test_make_order_by_deterministic.py --- old/SQLAlchemy-Utils-0.32.9/tests/functions/test_make_order_by_deterministic.py 2016-05-20 08:58:31.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/functions/test_make_order_by_deterministic.py 2016-10-20 10:04:40.000000000 +0200 @@ -46,58 +46,58 @@ def test_column_property(self, session, User): query = session.query(User).order_by(User.email_lower) query = make_order_by_deterministic(query) - assert_contains('lower("user".name) AS lower_1', query) - assert_contains('lower_1, "user".id ASC', query) + assert_contains('lower(user.name) AS lower_1', query) + assert_contains('lower_1, user.id ASC', query) def test_unique_column(self, session, User): query = session.query(User).order_by(User.email) query = make_order_by_deterministic(query) - assert str(query).endswith('ORDER BY "user".email') + assert str(query).endswith('ORDER BY user.email') def test_non_unique_column(self, session, User): query = session.query(User).order_by(User.name) query = make_order_by_deterministic(query) - assert_contains('ORDER BY "user".name, "user".id ASC', query) + assert_contains('ORDER BY user.name, user.id ASC', query) def test_descending_order_by(self, session, User): query = session.query(User).order_by( sa.desc(User.name) ) query = make_order_by_deterministic(query) - assert_contains('ORDER BY "user".name DESC, "user".id DESC', query) + assert_contains('ORDER BY user.name DESC, user.id DESC', query) def test_ascending_order_by(self, session, User): query = session.query(User).order_by( sa.asc(User.name) ) query = make_order_by_deterministic(query) - assert_contains('ORDER BY "user".name ASC, "user".id ASC', query) + assert_contains('ORDER BY user.name ASC, user.id ASC', query) def test_string_order_by(self, session, User): query = session.query(User).order_by('name') query = make_order_by_deterministic(query) - assert_contains('ORDER BY "user".name, "user".id ASC', query) + assert_contains('ORDER BY user.name, user.id ASC', query) def test_annotated_label(self, session, User): query = session.query(User).order_by(User.article_count) query = make_order_by_deterministic(query) - assert_contains('article_count, "user".id ASC', query) + assert_contains('article_count, user.id ASC', query) def test_annotated_label_with_descending_order(self, session, User): query = session.query(User).order_by( sa.desc(User.article_count) ) query = make_order_by_deterministic(query) - assert_contains('ORDER BY article_count DESC, "user".id DESC', query) + assert_contains('ORDER BY article_count DESC, user.id DESC', query) def test_query_without_order_by(self, session, User): query = session.query(User) query = make_order_by_deterministic(query) - assert 'ORDER BY "user".id' in str(query) + assert 'ORDER BY user.id' in str(query) def test_alias(self, session, User): alias = sa.orm.aliased(User.__table__) query = session.query(alias).order_by(alias.c.name) query = make_order_by_deterministic(query) - assert str(query).endswith('ORDER BY user_1.name, "user".id ASC') + assert str(query).endswith('ORDER BY user_1.name, user.id ASC') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/test_auto_delete_orphans.py new/SQLAlchemy-Utils-0.32.14/tests/test_auto_delete_orphans.py --- old/SQLAlchemy-Utils-0.32.9/tests/test_auto_delete_orphans.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/test_auto_delete_orphans.py 2016-10-20 09:13:46.000000000 +0200 @@ -1,5 +1,6 @@ import pytest import sqlalchemy as sa +from sqlalchemy.orm import backref from sqlalchemy_utils import auto_delete_orphans, ImproperlyConfigured @@ -36,17 +37,20 @@ return Tag -@pytest.fixture -def Entry(Base, Tag, tagging_tbl): +@pytest.fixture( + params=['entries', backref('entries', lazy='select')], + ids=['backref_string', 'backref_with_keywords'] +) +def Entry(Base, Tag, tagging_tbl, request): class Entry(Base): __tablename__ = 'entry' id = sa.Column(sa.Integer, primary_key=True) tags = sa.orm.relationship( - 'Tag', + Tag, secondary=tagging_tbl, - backref='entries' + backref=request.param ) auto_delete_orphans(Entry.tags) return Entry @@ -60,7 +64,7 @@ id = sa.Column(sa.Integer, primary_key=True) tags = sa.orm.relationship( - 'Tag', + Tag, secondary=tagging_tbl ) return EntryWithoutTagsBackref diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/test_case_insensitive_comparator.py new/SQLAlchemy-Utils-0.32.14/tests/test_case_insensitive_comparator.py --- old/SQLAlchemy-Utils-0.32.9/tests/test_case_insensitive_comparator.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/test_case_insensitive_comparator.py 2016-10-20 09:47:38.000000000 +0200 @@ -29,7 +29,7 @@ .filter(User.email == u'email@example.com') ) - assert '"user".email = lower(:lower_1)' in str(query) + assert 'user.email = lower(?)' in str(query) def test_supports_in_(self, session, User): query = ( @@ -37,7 +37,7 @@ .filter(User.email.in_([u'email@example.com', u'a'])) ) assert ( - '"user".email IN (lower(:lower_1), lower(:lower_2))' + 'user.email IN (lower(?), lower(?))' in str(query) ) @@ -47,7 +47,7 @@ .filter(User.email.notin_([u'email@example.com', u'a'])) ) assert ( - '"user".email NOT IN (lower(:lower_1), lower(:lower_2))' + 'user.email NOT IN (lower(?), lower(?))' in str(query) ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/test_expressions.py new/SQLAlchemy-Utils-0.32.14/tests/test_expressions.py --- old/SQLAlchemy-Utils-0.32.9/tests/test_expressions.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/test_expressions.py 2016-10-20 09:52:40.000000000 +0200 @@ -118,8 +118,9 @@ class TestRowToJson(object): def test_compiler_with_default_dialect(self): - with pytest.raises(sa.exc.CompileError): - str(row_to_json(sa.text('article.*'))) + assert str(row_to_json(sa.text('article.*'))) == ( + 'row_to_json(article.*)' + ) def test_compiler_with_postgresql(self): assert str(row_to_json(sa.text('article.*')).compile( @@ -135,13 +136,14 @@ class TestArrayAgg(object): def test_compiler_with_default_dialect(self): - with pytest.raises(sa.exc.CompileError): - str(sa.func.array_agg(sa.text('u.name'))) + assert str(sa.func.array_agg(sa.text('u.name'))) == ( + 'array_agg(u.name)' + ) def test_compiler_with_postgresql(self): assert str(sa.func.array_agg(sa.text('u.name')).compile( dialect=postgresql.dialect() - )) == "array_agg(u.name)" + )) == 'array_agg(u.name)' def test_type(self): assert isinstance( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/test_models.py new/SQLAlchemy-Utils-0.32.14/tests/test_models.py --- old/SQLAlchemy-Utils-0.32.9/tests/test_models.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/test_models.py 2016-12-18 10:08:56.000000000 +0100 @@ -1,21 +1,20 @@ +import sys from datetime import datetime import pytest import sqlalchemy as sa -from sqlalchemy_utils import Timestamp - - -@pytest.fixture -def Article(Base): - class Article(Base, Timestamp): - __tablename__ = 'article' - id = sa.Column(sa.Integer, primary_key=True) - name = sa.Column(sa.Unicode(255), default=u'Some article') - return Article +from sqlalchemy_utils import generic_repr, Timestamp class TestTimestamp(object): + @pytest.fixture + def Article(self, Base): + class Article(Base, Timestamp): + __tablename__ = 'article' + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.Unicode(255), default=u'Some article') + return Article def test_created(self, session, Article): then = datetime.utcnow() @@ -38,3 +37,52 @@ session.commit() assert article.updated >= then and article.updated <= datetime.utcnow() + + +class TestGenericRepr: + @pytest.fixture + def Article(self, Base): + class Article(Base): + __tablename__ = 'article' + id = sa.Column(sa.Integer, primary_key=True) + name = sa.Column(sa.Unicode(255), default=u'Some article') + return Article + + def test_repr(self, Article): + """Representation of a basic model.""" + Article = generic_repr(Article) + article = Article(id=1, name=u'Foo') + if sys.version_info[0] == 2: + expected_repr = u'Article(id=1, name=u\'Foo\')' + elif sys.version_info[0] == 3: + expected_repr = u'Article(id=1, name=\'Foo\')' + else: + raise AssertionError + actual_repr = repr(article) + + assert actual_repr == expected_repr + + def test_repr_partial(self, Article): + """Representation of a basic model with selected fields.""" + Article = generic_repr('id')(Article) + article = Article(id=1, name=u'Foo') + expected_repr = u'Article(id=1)' + actual_repr = repr(article) + + assert actual_repr == expected_repr + + def test_not_loaded(self, session, Article): + """:py:func:`~sqlalchemy_utils.models.generic_repr` doesn't force + execution of additional queries if some fields are not loaded and + instead represents them as "<not loaded>". + """ + Article = generic_repr(Article) + article = Article(name=u'Foo') + session.add(article) + session.commit() + + article = session.query(Article).options(sa.orm.defer('name')).one() + actual_repr = repr(article) + + expected_repr = u'Article(id={}, name=<not loaded>)'.format(article.id) + assert actual_repr == expected_repr diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/test_proxy_dict.py new/SQLAlchemy-Utils-0.32.14/tests/test_proxy_dict.py --- old/SQLAlchemy-Utils-0.32.9/tests/test_proxy_dict.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/test_proxy_dict.py 2016-10-20 09:15:43.000000000 +0200 @@ -13,7 +13,6 @@ id = sa.Column( sa.Integer, sa.ForeignKey('article.id'), - autoincrement=True, primary_key=True ) locale = sa.Column(sa.String(10), primary_key=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/test_sort_query.py new/SQLAlchemy-Utils-0.32.14/tests/test_sort_query.py --- old/SQLAlchemy-Utils-0.32.9/tests/test_sort_query.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/test_sort_query.py 2016-10-20 09:54:03.000000000 +0200 @@ -230,7 +230,7 @@ ) query = sort_query(query, 'aliased-full_name') assert_contains( - 'concat(aliased.title, :param_1, aliased.name)', query + 'concat(aliased.title, %(concat_1)s, aliased.name)', query ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/test_translation_hybrid.py new/SQLAlchemy-Utils-0.32.14/tests/test_translation_hybrid.py --- old/SQLAlchemy-Utils-0.32.9/tests/test_translation_hybrid.py 2016-04-25 16:20:06.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/test_translation_hybrid.py 2016-10-20 09:57:03.000000000 +0200 @@ -96,7 +96,7 @@ assert ( 'coalesce(article.name_translations -> article.locale' - in str(Article.name) + in str(Article.name.expression) ) def test_locales_casted_only_in_compilation_phase(self, Base): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/types/test_password.py new/SQLAlchemy-Utils-0.32.14/tests/types/test_password.py --- old/SQLAlchemy-Utils-0.32.9/tests/types/test_password.py 2016-04-25 16:21:32.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/types/test_password.py 2016-11-19 09:37:07.000000000 +0100 @@ -1,6 +1,10 @@ import mock import pytest import sqlalchemy as sa +import sqlalchemy.dialects.mysql +import sqlalchemy.dialects.oracle +import sqlalchemy.dialects.postgresql +import sqlalchemy.dialects.sqlite from sqlalchemy import inspect from sqlalchemy_utils import Password, PasswordType, types # noqa @@ -52,6 +56,23 @@ @pytest.mark.skipif('types.password.passlib is None') class TestPasswordType(object): + @pytest.mark.parametrize('dialect_module,impl', [ + (sqlalchemy.dialects.sqlite, sa.dialects.sqlite.BLOB), + (sqlalchemy.dialects.postgresql, sa.dialects.postgresql.BYTEA), + (sqlalchemy.dialects.oracle, sa.dialects.oracle.RAW), + (sqlalchemy.dialects.mysql, sa.VARBINARY), + ]) + def test_load_dialect_impl(self, dialect_module, impl): + """ + Should produce the same impl type as Alembic would expect after + inspecing a database + """ + password_type = PasswordType() + assert isinstance( + password_type.load_dialect_impl(dialect_module.dialect()), + impl + ) + def test_encrypt(self, User): """Should encrypt the password on setting the attribute.""" obj = User() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/types/test_phonenumber.py new/SQLAlchemy-Utils-0.32.14/tests/types/test_phonenumber.py --- old/SQLAlchemy-Utils-0.32.9/tests/types/test_phonenumber.py 2016-05-30 11:49:18.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/types/test_phonenumber.py 2016-10-20 08:53:21.000000000 +0200 @@ -150,6 +150,9 @@ session.refresh(user) assert user.phone_number is None + def test_uses_phonenumber_class_as_python_type(self): + assert PhoneNumberType().python_type is PhoneNumber + @pytest.mark.usefixtures('user') def test_phone_number_is_none(self, session, User): phone_number = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.9/tests/types/test_timezone.py new/SQLAlchemy-Utils-0.32.14/tests/types/test_timezone.py --- old/SQLAlchemy-Utils-0.32.9/tests/types/test_timezone.py 2016-04-25 16:20:07.000000000 +0200 +++ new/SQLAlchemy-Utils-0.32.14/tests/types/test_timezone.py 2016-11-19 09:35:59.000000000 +0100 @@ -1,7 +1,9 @@ import pytest +import pytz import sqlalchemy as sa +from dateutil.zoneinfo import getzoneinfofile_stream, tzfile, ZoneInfoFile -from sqlalchemy_utils.types import timezone +from sqlalchemy_utils.types import timezone, TimezoneType @pytest.fixture @@ -46,3 +48,48 @@ assert visitor_dateutil is not None assert visitor_pytz is not None + + +TIMEZONE_BACKENDS = ['dateutil', 'pytz'] + + +def test_can_coerce_pytz_DstTzInfo(): + tzcol = TimezoneType(backend='pytz') + tz = pytz.timezone('America/New_York') + assert isinstance(tz, pytz.tzfile.DstTzInfo) + assert tzcol._coerce(tz) is tz + + +def test_can_coerce_pytz_StaticTzInfo(): + tzcol = TimezoneType(backend='pytz') + tz = pytz.timezone('Pacific/Truk') + assert isinstance(tz, pytz.tzfile.StaticTzInfo) + assert tzcol._coerce(tz) is tz + + +@pytest.mark.parametrize('zone', pytz.all_timezones) +def test_can_coerce_string_for_pytz_zone(zone): + tzcol = TimezoneType(backend='pytz') + assert tzcol._coerce(zone).zone == zone + + +@pytest.mark.parametrize( + 'zone', ZoneInfoFile(getzoneinfofile_stream()).zones.keys()) +def test_can_coerce_string_for_dateutil_zone(zone): + tzcol = TimezoneType(backend='dateutil') + assert isinstance(tzcol._coerce(zone), tzfile) + + +@pytest.mark.parametrize('backend', TIMEZONE_BACKENDS) +def test_can_coerce_and_raise_UnknownTimeZoneError_or_ValueError(backend): + tzcol = TimezoneType(backend=backend) + with pytest.raises((ValueError, pytz.exceptions.UnknownTimeZoneError)): + tzcol._coerce('SolarSystem/Mars') + with pytest.raises((ValueError, pytz.exceptions.UnknownTimeZoneError)): + tzcol._coerce('') + + +@pytest.mark.parametrize('backend', TIMEZONE_BACKENDS) +def test_can_coerce_None(backend): + tzcol = TimezoneType(backend=backend) + assert tzcol._coerce(None) is None