Hello community,

here is the log from the commit of package python-SQLAlchemy-Utils for 
openSUSE:Factory checked in at 2016-11-24 21:22:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-SQLAlchemy-Utils (Old)
 and      /work/SRC/openSUSE:Factory/.python-SQLAlchemy-Utils.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-SQLAlchemy-Utils"

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-SQLAlchemy-Utils/python-SQLAlchemy-Utils.changes
  2016-10-10 16:20:03.000000000 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-SQLAlchemy-Utils.new/python-SQLAlchemy-Utils.changes
     2016-11-24 21:22:41.000000000 +0100
@@ -1,0 +2,7 @@
+Mon Nov 14 13:59:20 UTC 2016 - [email protected]
+
+- udpate to 0.32.9:
+  - Added support for multi-column observers (#231, pull request courtesy of 
quantus)
+  - Fixed EmailType to respect constructor args (#230, pull request courtesy 
of quantus)
+
+-------------------------------------------------------------------

Old:
----
  SQLAlchemy-Utils-0.32.7.tar.gz

New:
----
  SQLAlchemy-Utils-0.32.9.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-SQLAlchemy-Utils.spec ++++++
--- /var/tmp/diff_new_pack.RVW1ch/_old  2016-11-24 21:22:42.000000000 +0100
+++ /var/tmp/diff_new_pack.RVW1ch/_new  2016-11-24 21:22:42.000000000 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python-SQLAlchemy-Utils
-Version:        0.32.7
+Version:        0.32.9
 Release:        0
 Summary:        Various utility functions for SQLAlchemy
 License:        BSD-3-Clause

++++++ SQLAlchemy-Utils-0.32.7.tar.gz -> SQLAlchemy-Utils-0.32.9.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.7/CHANGES.rst 
new/SQLAlchemy-Utils-0.32.9/CHANGES.rst
--- old/SQLAlchemy-Utils-0.32.7/CHANGES.rst     2016-05-20 09:01:16.000000000 
+0200
+++ new/SQLAlchemy-Utils-0.32.9/CHANGES.rst     2016-07-17 22:00:14.000000000 
+0200
@@ -4,6 +4,18 @@
 Here you can see the full list of changes between each SQLAlchemy-Utils 
release.
 
 
+0.32.9 (2016-07-17)
+^^^^^^^^^^^^^^^^^^^
+
+- Added support for multi-column observers (#231, pull request courtesy of 
quantus)
+
+
+0.32.8 (2016-05-20)
+^^^^^^^^^^^^^^^^^^^
+
+- Fixed EmailType to respect constructor args (#230, pull request courtesy of 
quantus)
+
+
 0.32.7 (2016-05-20)
 ^^^^^^^^^^^^^^^^^^^
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.7/PKG-INFO 
new/SQLAlchemy-Utils-0.32.9/PKG-INFO
--- old/SQLAlchemy-Utils-0.32.7/PKG-INFO        2016-05-30 11:40:44.000000000 
+0200
+++ new/SQLAlchemy-Utils-0.32.9/PKG-INFO        2016-07-17 22:02:35.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: SQLAlchemy-Utils
-Version: 0.32.7
+Version: 0.32.9
 Summary: Various utility functions for SQLAlchemy.
 Home-page: https://github.com/kvesteri/sqlalchemy-utils
 Author: Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.7/README.rst 
new/SQLAlchemy-Utils-0.32.9/README.rst
--- old/SQLAlchemy-Utils-0.32.7/README.rst      2015-08-16 10:02:58.000000000 
+0200
+++ new/SQLAlchemy-Utils-0.32.9/README.rst      2016-07-10 11:03:30.000000000 
+0200
@@ -10,7 +10,7 @@
 Resources
 ---------
 
-- `Documentation <http://sqlalchemy-utils.readthedocs.org/>`_
+- `Documentation <https://sqlalchemy-utils.readthedocs.io/>`_
 - `Issue Tracker <http://github.com/kvesteri/sqlalchemy-utils/issues>`_
 - `Code <http://github.com/kvesteri/sqlalchemy-utils/>`_
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/SQLAlchemy-Utils-0.32.7/SQLAlchemy_Utils.egg-info/PKG-INFO 
new/SQLAlchemy-Utils-0.32.9/SQLAlchemy_Utils.egg-info/PKG-INFO
--- old/SQLAlchemy-Utils-0.32.7/SQLAlchemy_Utils.egg-info/PKG-INFO      
2016-05-30 11:40:43.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/SQLAlchemy_Utils.egg-info/PKG-INFO      
2016-07-17 22:02:34.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: SQLAlchemy-Utils
-Version: 0.32.7
+Version: 0.32.9
 Summary: Various utility functions for SQLAlchemy.
 Home-page: https://github.com/kvesteri/sqlalchemy-utils
 Author: Konsta Vesterinen, Ryan Leckey, Janne Vanhala, Vesa Uimonen
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.7/docs/installation.rst 
new/SQLAlchemy-Utils-0.32.9/docs/installation.rst
--- old/SQLAlchemy-Utils-0.32.7/docs/installation.rst   2016-01-20 
08:23:47.000000000 +0100
+++ new/SQLAlchemy-Utils-0.32.9/docs/installation.rst   2016-07-17 
22:00:22.000000000 +0200
@@ -8,7 +8,7 @@
 
 SQLAlchemy-Utils has been tested against the following Python platforms.
 
-- cPython 2.6
+- cPython 2.6 (unsupported since 0.32)
 - cPython 2.7
 - cPython 3.3
 - cPython 3.4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.7/setup.cfg 
new/SQLAlchemy-Utils-0.32.9/setup.cfg
--- old/SQLAlchemy-Utils-0.32.7/setup.cfg       2016-05-30 11:40:44.000000000 
+0200
+++ new/SQLAlchemy-Utils-0.32.9/setup.cfg       2016-07-17 22:02:35.000000000 
+0200
@@ -1,5 +1,5 @@
 [egg_info]
+tag_date = 0
 tag_build = 
 tag_svn_revision = 0
-tag_date = 0
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/__init__.py 
new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/__init__.py
--- old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/__init__.py    2016-05-20 
09:01:30.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/__init__.py    2016-07-17 
22:00:12.000000000 +0200
@@ -95,4 +95,4 @@
     WeekDaysType
 )
 
-__version__ = '0.32.7'
+__version__ = '0.32.9'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/observer.py 
new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/observer.py
--- old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/observer.py    2016-04-25 
16:20:06.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/observer.py    2016-07-17 
22:00:22.000000000 +0200
@@ -148,6 +148,29 @@
     session.commit()
     catalog.product_count  # 1
 
+
+Observing multiple columns
+-----------------------
+
+You can also observe multiple columns by spesifying all the observable columns
+in the decorator.
+
+
+::
+
+    class Order(Base):
+        __tablename__ = 'order'
+        id = sa.Column(sa.Integer, primary_key=True)
+        unit_price = sa.Column(sa.Integer)
+        amount = sa.Column(sa.Integer)
+        total_price = sa.Column(sa.Integer)
+
+        @observes('amount', 'unit_price')
+        def total_price_observer(self, amount, unit_price):
+            self.total_price = amount * unit_price
+
+
+
 """
 import itertools
 from collections import defaultdict, Iterable, namedtuple
@@ -158,7 +181,7 @@
 from .path import AttrPath
 from .utils import is_sequence
 
-Callback = namedtuple('Callback', ['func', 'path', 'backref', 'fullpath'])
+Callback = namedtuple('Callback', ['func', 'backref', 'fullpath'])
 
 
 class PropertyObserver(object):
@@ -208,32 +231,33 @@
                 )
 
     def gather_paths(self):
-        for class_, callbacks in self.generator_registry.items():
-            for callback in callbacks:
-                path = AttrPath(class_, callback.__observes__)
-
-                self.callback_map[class_].append(
-                    Callback(
-                        func=callback,
-                        path=path,
-                        backref=None,
-                        fullpath=path
+        for class_, generators in self.generator_registry.items():
+            for callback in generators:
+                full_paths = []
+                for call_path in callback.__observes__:
+                    full_paths.append(AttrPath(class_, call_path))
+
+                for path in full_paths:
+                    self.callback_map[class_].append(
+                        Callback(
+                            func=callback,
+                            backref=None,
+                            fullpath=full_paths
+                        )
                     )
-                )
 
-                for index in range(len(path)):
-                    i = index + 1
-                    prop = path[index].property
-                    if isinstance(prop, sa.orm.RelationshipProperty):
-                        prop_class = path[index].property.mapper.class_
-                        self.callback_map[prop_class].append(
-                            Callback(
-                                func=callback,
-                                path=path[i:],
-                                backref=~ (path[:i]),
-                                fullpath=path
+                    for index in range(len(path)):
+                        i = index + 1
+                        prop = path[index].property
+                        if isinstance(prop, sa.orm.RelationshipProperty):
+                            prop_class = path[index].property.mapper.class_
+                            self.callback_map[prop_class].append(
+                                Callback(
+                                    func=callback,
+                                    backref=~ (path[:i]),
+                                    fullpath=full_paths
+                                )
                             )
-                        )
 
     def gather_callback_args(self, obj, callbacks):
         for callback in callbacks:
@@ -252,18 +276,19 @@
 
     def get_callback_args(self, root_obj, callback):
         session = sa.orm.object_session(root_obj)
-        objects = getdotattr(
+        objects = [getdotattr(
             root_obj,
-            callback.fullpath,
+            path,
             lambda obj: obj not in session.deleted
-        )
-        path = str(callback.fullpath)
-        if '.' in path or has_changes(root_obj, path):
-            return (
-                root_obj,
-                callback.func,
-                objects
-            )
+        ) for path in callback.fullpath]
+        paths = [str(path) for path in callback.fullpath]
+        for path in paths:
+            if '.' in path or has_changes(root_obj, path):
+                return (
+                    root_obj,
+                    callback.func,
+                    objects
+                )
 
     def iterate_objects_and_callbacks(self, session):
         objs = itertools.chain(session.new, session.dirty, session.deleted)
@@ -277,21 +302,25 @@
         for obj, callbacks in self.iterate_objects_and_callbacks(session):
             args = self.gather_callback_args(obj, callbacks)
             for (root_obj, func, objects) in args:
-                if is_sequence(objects):
-                    callback_args[root_obj][func] = (
-                        callback_args[root_obj][func] | set(objects)
-                    )
-                else:
-                    callback_args[root_obj][func] = objects
+                if not callback_args[root_obj][func]:
+                    callback_args[root_obj][func] = {}
+                for i, object_ in enumerate(objects):
+                    if is_sequence(object_):
+                        callback_args[root_obj][func][i] = (
+                            callback_args[root_obj][func].get(i, set()) |
+                            set(object_)
+                        )
+                    else:
+                        callback_args[root_obj][func][i] = object_
 
         for root_obj, callback_objs in callback_args.items():
             for callback, objs in callback_objs.items():
-                callback(root_obj, objs)
+                callback(root_obj, *[objs[i] for i in range(len(objs))])
 
 observer = PropertyObserver()
 
 
-def observes(path, observer=observer):
+def observes(*paths, **observer_kw):
     """
     Mark method as property observer for the given property path. Inside
     transaction observer gathers all changes made in given property path and
@@ -327,14 +356,17 @@
 
     .. versionadded: 0.28.0
 
-    :param path: Dot-notated property path, eg. 'categories.products.price'
-    :param observer: :meth:`PropertyObserver` object
+    :param *paths: One or more dot-notated property paths, eg.
+       'categories.products.price'
+    :param **observer: A dictionary where value for key 'observer' contains
+       :meth:`PropertyObserver` object
     """
-    observer.register_listeners()
+    observer_ = observer_kw.pop('observer', observer)
+    observer_.register_listeners()
 
     def wraps(func):
         def wrapper(self, *args, **kwargs):
             return func(self, *args, **kwargs)
-        wrapper.__observes__ = path
+        wrapper.__observes__ = paths
         return wrapper
     return wraps
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/types/email.py 
new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/email.py
--- old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/types/email.py 2016-04-25 
16:20:06.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/email.py 2016-07-10 
11:03:30.000000000 +0200
@@ -4,9 +4,12 @@
 
 
 class EmailType(sa.types.TypeDecorator):
-    impl = sa.Unicode(255)
+    impl = sa.Unicode
     comparator_factory = CaseInsensitiveComparator
 
+    def __init__(self, length=255, *args, **kwargs):
+        super(EmailType, self).__init__(length=length, *args, **kwargs)
+
     def process_bind_param(self, value, dialect):
         if value is not None:
             return value.lower()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/types/encrypted.py 
new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/encrypted.py
--- old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/types/encrypted.py     
2016-04-25 16:20:06.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/encrypted.py     
2016-07-10 11:03:30.000000000 +0200
@@ -13,7 +13,7 @@
     import cryptography
     from cryptography.hazmat.backends import default_backend
     from cryptography.hazmat.primitives import hashes
-    from cryptography.hazmat.primitives.ciphers import(
+    from cryptography.hazmat.primitives.ciphers import (
         Cipher, algorithms, modes
     )
     from cryptography.fernet import Fernet
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/types/password.py 
new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/password.py
--- old/SQLAlchemy-Utils-0.32.7/sqlalchemy_utils/types/password.py      
2016-04-25 16:20:06.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/sqlalchemy_utils/types/password.py      
2016-07-10 11:03:30.000000000 +0200
@@ -79,13 +79,14 @@
 class PasswordType(types.TypeDecorator, ScalarCoercible):
     """
     PasswordType hashes passwords as they come into the database and allows
-    verifying them using a pythonic interface.
+    verifying them using a Pythonic interface. This Pythonic interface
+    relies on setting up automatic data type coercison using the
+    :func:`~sqlalchemy_utils.listeners.force_auto_coercion` function.
 
     All keyword arguments (aside from max_length) are forwarded to the
     construction of a `passlib.context.LazyCryptContext` object, which
     also supports deferred configuration via the `onload` callback.
 
-
     The following usage will create a password column that will
     automatically hash new passwords as `pbkdf2_sha512` but still compare
     passwords against pre-existing `md5_crypt` hashes. As passwords are
@@ -124,6 +125,9 @@
 
 
         import flask
+        from sqlalchemy_utils import PasswordType, force_auto_coercion
+
+        force_auto_coercion()
 
         class User(db.Model):
             __tablename__ = 'user'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/SQLAlchemy-Utils-0.32.7/tests/observes/test_column_property.py 
new/SQLAlchemy-Utils-0.32.9/tests/observes/test_column_property.py
--- old/SQLAlchemy-Utils-0.32.7/tests/observes/test_column_property.py  
2016-04-25 16:20:06.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/tests/observes/test_column_property.py  
2016-07-17 22:00:22.000000000 +0200
@@ -58,3 +58,75 @@
             product.price = 500
             session.commit()
         assert str(e.value) == 'Trying to change price'
+
+
[email protected]('postgresql_dsn')
+class TestObservesForMultipleColumns(object):
+
+    @pytest.fixture
+    def Order(self, Base):
+        class Order(Base):
+            __tablename__ = 'order'
+            id = sa.Column(sa.Integer, primary_key=True)
+            unit_price = sa.Column(sa.Integer)
+            amount = sa.Column(sa.Integer)
+            total_price = sa.Column(sa.Integer)
+
+            @observes('amount', 'unit_price')
+            def total_price_observer(self, amount, unit_price):
+                self.total_price = amount * unit_price
+        return Order
+
+    @pytest.fixture
+    def init_models(self, Order):
+        pass
+
+    def test_only_notifies_observer_on_actual_changes(self, session, Order):
+        order = Order()
+        order.amount = 2
+        order.unit_price = 10
+        session.add(order)
+        session.flush()
+
+        order.amount = 1
+        session.flush()
+        assert order.total_price == 10
+
+        order.unit_price = 100
+        session.flush()
+        assert order.total_price == 100
+
+
[email protected]('postgresql_dsn')
+class TestObservesForMultipleColumnsFiresOnlyOnce(object):
+
+    @pytest.fixture
+    def Order(self, Base):
+        class Order(Base):
+            __tablename__ = 'order'
+            id = sa.Column(sa.Integer, primary_key=True)
+            unit_price = sa.Column(sa.Integer)
+            amount = sa.Column(sa.Integer)
+
+            @observes('amount', 'unit_price')
+            def total_price_observer(self, amount, unit_price):
+                self.call_count = self.call_count + 1
+        return Order
+
+    @pytest.fixture
+    def init_models(self, Order):
+        pass
+
+    def test_only_notifies_observer_on_actual_changes(self, session, Order):
+        order = Order()
+        order.amount = 2
+        order.unit_price = 10
+        order.call_count = 0
+        session.add(order)
+        session.flush()
+        assert order.call_count == 1
+
+        order.amount = 1
+        order.unit_price = 100
+        session.flush()
+        assert order.call_count == 2
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/SQLAlchemy-Utils-0.32.7/tests/types/test_email.py 
new/SQLAlchemy-Utils-0.32.9/tests/types/test_email.py
--- old/SQLAlchemy-Utils-0.32.7/tests/types/test_email.py       2016-04-25 
16:20:07.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/tests/types/test_email.py       2016-07-10 
11:03:30.000000000 +0200
@@ -10,6 +10,7 @@
         __tablename__ = 'user'
         id = sa.Column(sa.Integer, primary_key=True)
         email = sa.Column(EmailType)
+        short_email = sa.Column(EmailType(length=70))
 
         def __repr__(self):
             return 'User(%r)' % self.id
@@ -30,3 +31,6 @@
         clause = User.email == '[email protected]'
         compiled = str(clause.compile(compile_kwargs={'literal_binds': True}))
         assert compiled == '"user".email = lower(\'[email protected]\')'
+
+    def test_custom_length(self, session, User):
+        assert User.short_email.type.impl.length == 70
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/SQLAlchemy-Utils-0.32.7/tests/types/test_phonenumber.py 
new/SQLAlchemy-Utils-0.32.9/tests/types/test_phonenumber.py
--- old/SQLAlchemy-Utils-0.32.7/tests/types/test_phonenumber.py 2016-05-20 
08:58:31.000000000 +0200
+++ new/SQLAlchemy-Utils-0.32.9/tests/types/test_phonenumber.py 2016-05-30 
11:49:18.000000000 +0200
@@ -90,8 +90,8 @@
         try:
             session.execute(
                 User.__table__.insert().values(
-                    name='Someone',
-                    phone_number='abc'
+                    name=u'Someone',
+                    phone_number=u'abc'
                 )
             )
         except PhoneNumberParseException:


Reply via email to