Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pony for openSUSE:Factory 
checked in at 2021-04-19 21:06:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pony (Old)
 and      /work/SRC/openSUSE:Factory/.python-pony.new.12324 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pony"

Mon Apr 19 21:06:01 2021 rev:6 rq:886564 version:0.7.14

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pony/python-pony.changes  2020-03-09 
11:42:43.125334956 +0100
+++ /work/SRC/openSUSE:Factory/.python-pony.new.12324/python-pony.changes       
2021-04-19 21:06:19.088058047 +0200
@@ -1,0 +2,21 @@
+Sun Apr 18 13:11:59 UTC 2021 - Ben Greiner <c...@bnavigator.de>
+
+- Update to 0.7.14
+  * Add Python 3.9 support
+  * Allow to use kwargs in select: Entity.select(**kwargs) and
+    obj.collection.select(**kwargs), a feature that was announced
+    but actually missed from 0.7.7
+  * Add support for volatile collection attributes that don't throw
+    "Phantom object appeared/disappeared" exceptions
+  * Fix negative timedelta conversions
+  * Pony should reconnect to PostgreSQL when receiving 57P01 error
+    (AdminShutdown)
+  * Allow mixing compatible types (like int and float) in
+    coalesce() arguments
+  * Support of subqueries in coalesce() arguments
+  * Fix using aggregated subqueries in ORDER BY section
+  * Fix queries with expressions like (x, y) in ((a, b), (c, d))
+  * #451: KeyError for seeds with unique attributes in
+    SessionCache.update_simple_index()
+
+-------------------------------------------------------------------

Old:
----
  pony-0.7.13.tar.gz

New:
----
  pony-0.7.14.tar.gz

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

Other differences:
------------------
++++++ python-pony.spec ++++++
--- /var/tmp/diff_new_pack.FDH0hD/_old  2021-04-19 21:06:19.592058803 +0200
+++ /var/tmp/diff_new_pack.FDH0hD/_new  2021-04-19 21:06:19.592058803 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package python-pony
 #
-# Copyright (c) 2020 SUSE LLC
+# Copyright (c) 2021 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-pony
-Version:        0.7.13
+Version:        0.7.14
 Release:        0
 Summary:        Pony Object-Relational Mapper
 License:        Apache-2.0
@@ -58,6 +58,7 @@
 %files %{python_files}
 %doc README.md
 %license LICENSE
-%{python_sitelib}/*
+%{python_sitelib}/pony
+%{python_sitelib}/pony-%{version}*-info
 
 %changelog

++++++ pony-0.7.13.tar.gz -> pony-0.7.14.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/PKG-INFO new/pony-0.7.14/PKG-INFO
--- old/pony-0.7.13/PKG-INFO    2020-03-03 14:51:21.000000000 +0100
+++ new/pony-0.7.14/PKG-INFO    2020-11-23 19:53:37.176609300 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pony
-Version: 0.7.13
+Version: 0.7.14
 Summary: Pony Object-Relational Mapper
 Home-page: https://ponyorm.com
 Author: Alexander Kozlovsky, Alexey Malashkevich
@@ -66,6 +66,7 @@
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Software Development :: Libraries
 Classifier: Topic :: Database
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/README.md new/pony-0.7.14/README.md
--- old/pony-0.7.13/README.md   2020-03-03 14:51:21.000000000 +0100
+++ new/pony-0.7.14/README.md   2020-10-24 14:49:39.000000000 +0200
@@ -1,32 +1,6 @@
 # Downloads
 [![Downloads](https://pepy.tech/badge/pony)](https://pepy.tech/project/pony) 
[![Downloads](https://pepy.tech/badge/pony/month)](https://pepy.tech/project/pony/month)
 
[![Downloads](https://pepy.tech/badge/pony/week)](https://pepy.tech/project/pony/week)
 
-# Tests
-
-#### PostgreSQL
-Python 2 <a 
href="http://jenkins.agilecode.io:8111/viewType.html?buildTypeId=GithubPonyORMCi_Python2postgres&guest=1";>
-<img 
src="http://jenkins.agilecode.io:8111/app/rest/builds/buildType:(id:GithubPonyORMCi_Python2postgres)/statusIcon"/>
-</a>
-Python 3 <a 
href="http://jenkins.agilecode.io:8111/viewType.html?buildTypeId=GithubPonyORMCi_Python3postgres&guest=1";>
-<img 
src="http://jenkins.agilecode.io:8111/app/rest/builds/buildType:(id:GithubPonyORMCi_Python3postgres)/statusIcon"/>
-</a>
-
-#### SQLite
-Python 2 <a 
href="http://jenkins.agilecode.io:8111/viewType.html?buildTypeId=GithubPonyORMCi_Python2sqlite&guest=1";>
-<img 
src="http://jenkins.agilecode.io:8111/app/rest/builds/buildType:(id:GithubPonyORMCi_Python2sqlite)/statusIcon"/>
-</a>
-Python 3 <a 
href="http://jenkins.agilecode.io:8111/viewType.html?buildTypeId=GithubPonyORMCi_Python3sqlite&guest=1";>
-<img 
src="http://jenkins.agilecode.io:8111/app/rest/builds/buildType:(id:GithubPonyORMCi_Python3sqlite)/statusIcon"/>
-</a>
-
-#### CockroachDB
-Python 2 <a 
href="http://jenkins.agilecode.io:8111/viewType.html?buildTypeId=GithubPonyORMCi_Python2cockroach&guest=1";>
-<img 
src="http://jenkins.agilecode.io:8111/app/rest/builds/buildType:(id:GithubPonyORMCi_Python2cockroach)/statusIcon"/>
-</a>
-Python 3 <a 
href="http://jenkins.agilecode.io:8111/viewType.html?buildTypeId=GithubPonyORMCi_Python3cockroach&guest=1";>
-<img 
src="http://jenkins.agilecode.io:8111/app/rest/builds/buildType:(id:GithubPonyORMCi_Python3cockroach)/statusIcon"/>
-</a>
-
 
 Pony Object-Relational Mapper
 =============================
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/__init__.py 
new/pony-0.7.14/pony/__init__.py
--- old/pony-0.7.13/pony/__init__.py    2020-03-03 14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/__init__.py    2020-11-23 19:35:11.000000000 +0100
@@ -1,52 +1,52 @@
-from __future__ import absolute_import, print_function
-
-import os, sys
-from os.path import dirname
-
-__version__ = '0.7.13'
-
-def detect_mode():
-    try: import google.appengine
-    except ImportError: pass
-    else:
-        if os.getenv('SERVER_SOFTWARE', '').startswith('Development'):
-            return 'GAE-LOCAL'
-        return 'GAE-SERVER'
-
-    try: from mod_wsgi import version
-    except: pass
-    else: return 'MOD_WSGI'
-
-    main = sys.modules['__main__']
-
-    if not hasattr(main, '__file__'): # console
-        return 'INTERACTIVE'
-
-    if os.getenv('IPYTHONENABLE', '') == 'True':
-        return 'INTERACTIVE'
-
-    if getattr(main, 'INTERACTIVE_MODE_AVAILABLE', False): # pycharm console
-        return 'INTERACTIVE'
-
-    if 'flup.server.fcgi' in sys.modules: return 'FCGI-FLUP'
-    if 'uwsgi' in sys.modules: return 'UWSGI'
-    if 'flask' in sys.modules: return 'FLASK'
-    if 'cherrypy' in sys.modules: return 'CHERRYPY'
-    if 'bottle' in sys.modules: return 'BOTTLE'
-    return 'UNKNOWN'
-
-MODE = detect_mode()
-
-MAIN_FILE = None
-if MODE == 'MOD_WSGI':
-    for module_name, module in sys.modules.items():
-        if module_name.startswith('_mod_wsgi_'):
-            MAIN_FILE = module.__file__
-            break
-elif MODE != 'INTERACTIVE':
-    MAIN_FILE = sys.modules['__main__'].__file__
-
-if MAIN_FILE is not None: MAIN_DIR = dirname(MAIN_FILE)
-else: MAIN_DIR = None
-
-PONY_DIR = dirname(__file__)
+from __future__ import absolute_import, print_function
+
+import os, sys
+from os.path import dirname
+
+__version__ = '0.7.14'
+
+def detect_mode():
+    try: import google.appengine
+    except ImportError: pass
+    else:
+        if os.getenv('SERVER_SOFTWARE', '').startswith('Development'):
+            return 'GAE-LOCAL'
+        return 'GAE-SERVER'
+
+    try: from mod_wsgi import version
+    except: pass
+    else: return 'MOD_WSGI'
+
+    main = sys.modules['__main__']
+
+    if not hasattr(main, '__file__'): # console
+        return 'INTERACTIVE'
+
+    if os.getenv('IPYTHONENABLE', '') == 'True':
+        return 'INTERACTIVE'
+
+    if getattr(main, 'INTERACTIVE_MODE_AVAILABLE', False): # pycharm console
+        return 'INTERACTIVE'
+
+    if 'flup.server.fcgi' in sys.modules: return 'FCGI-FLUP'
+    if 'uwsgi' in sys.modules: return 'UWSGI'
+    if 'flask' in sys.modules: return 'FLASK'
+    if 'cherrypy' in sys.modules: return 'CHERRYPY'
+    if 'bottle' in sys.modules: return 'BOTTLE'
+    return 'UNKNOWN'
+
+MODE = detect_mode()
+
+MAIN_FILE = None
+if MODE == 'MOD_WSGI':
+    for module_name, module in sys.modules.items():
+        if module_name.startswith('_mod_wsgi_'):
+            MAIN_FILE = module.__file__
+            break
+elif MODE != 'INTERACTIVE':
+    MAIN_FILE = sys.modules['__main__'].__file__
+
+if MAIN_FILE is not None: MAIN_DIR = dirname(MAIN_FILE)
+else: MAIN_DIR = None
+
+PONY_DIR = dirname(__file__)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/converting.py 
new/pony-0.7.14/pony/converting.py
--- old/pony-0.7.13/pony/converting.py  2020-03-03 14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/converting.py  2020-10-24 14:49:39.000000000 +0200
@@ -201,13 +201,14 @@
     return int(hh), int(mm or 0), int(ss or 0), int(mcs)
 
 def str2timedelta(s):
+    negative = s.startswith('-')
     if '.' in s:
         s, fractional = s.split('.')
         microseconds = int((fractional + '000000')[:6])
     else: microseconds = 0
     h, m, s = imap(int, s.split(':'))
     td = timedelta(hours=abs(h), minutes=m, seconds=s, 
microseconds=microseconds)
-    return -td if h < 0 else td
+    return -td if negative else td
 
 def timedelta2str(td):
     total_seconds = td.days * (24 * 60 * 60) + td.seconds
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/core.py 
new/pony-0.7.14/pony/orm/core.py
--- old/pony-0.7.13/pony/orm/core.py    2020-03-03 14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/orm/core.py    2020-11-23 18:56:27.000000000 +0100
@@ -298,11 +298,11 @@
         result.entities_to_prefetch = self.entities_to_prefetch.copy()
         return result
     def __enter__(self):
-        assert local.prefetch_context is None
-        local.prefetch_context = self
+        local.prefetch_context_stack.append(self)
     def __exit__(self, exc_type, exc_val, exc_tb):
-        assert local.prefetch_context is self
-        local.prefetch_context = None
+        stack = local.prefetch_context_stack
+        assert stack and stack[-1] is self
+        stack.pop()
     def get_frozen_attrs_to_prefetch(self, entity):
         attrs_to_prefetch = self.attrs_to_prefetch_dict.get(entity, ())
         if type(attrs_to_prefetch) is set:
@@ -329,11 +329,16 @@
         local.db2cache = {}
         local.db_context_counter = 0
         local.db_session = None
-        local.prefetch_context = None
+        local.prefetch_context_stack = []
         local.current_user = None
         local.perms_context = None
         local.user_groups_cache = {}
         local.user_roles_cache = defaultdict(dict)
+    @property
+    def prefetch_context(local):
+        if local.prefetch_context_stack:
+            return local.prefetch_context_stack[-1]
+        return None
     def push_debug_state(local, debug, show_values):
         local.debug_stack.append((local.debug, local.show_values))
         if not suppress_debug_change:
@@ -1947,7 +1952,10 @@
             obj2 = cache_index.setdefault(new_val, obj)
             if obj2 is not obj: throw(CacheIndexError, 'Cannot update %s.%s: 
%s with key %s already exists'
                                                  % (obj.__class__.__name__, 
attr.name, obj2, new_val))
-        if old_val is not None: del cache_index[old_val]
+        if old_val is NOT_LOADED:
+            old_val = None
+        if old_val is not None:
+            del cache_index[old_val]
         undo.append((cache_index, old_val, new_val))
     def db_update_simple_index(cache, obj, attr, old_dbval, new_dbval):
         if old_dbval == new_dbval: return
@@ -2140,7 +2148,7 @@
         if attr.py_type == float:
             if attr.is_pk: throw(TypeError, 'PrimaryKey attribute %s cannot be 
of type float' % attr)
             elif attr.is_unique: throw(TypeError, 'Unique attribute %s cannot 
be of type float' % attr)
-        if attr.is_volatile and (attr.is_pk or attr.is_collection): 
throw(TypeError,
+        if attr.is_volatile and attr.is_pk: throw(TypeError,
             '%s attribute %s cannot be volatile' % (attr.__class__.__name__, 
attr))
 
         if attr.interleave is not None:
@@ -2150,6 +2158,8 @@
                 '`interleave` option value should be True, False or None. Got: 
%r' % attr.interleave)
     def linked(attr):
         reverse = attr.reverse
+        if reverse.is_volatile:
+            attr.is_volatile = True
         if attr.cascade_delete is None:
             attr.cascade_delete = attr.is_collection and reverse.is_required
         elif attr.cascade_delete:
@@ -2867,7 +2877,7 @@
                     else:
                         phantoms = setdata2 - items
                         if setdata2.added: phantoms -= setdata2.added
-                        if phantoms: throw(UnrepeatableReadError,
+                        if phantoms and not attr.is_volatile: 
throw(UnrepeatableReadError,
                             'Phantom object %s disappeared from collection 
%s.%s'
                             % (safe_repr(phantoms.pop()), safe_repr(obj2), 
attr.name))
                     items -= setdata2
@@ -2889,7 +2899,8 @@
         assert obj._status_ not in del_statuses
         setdata = obj._vals_.get(attr)
         if setdata is None: setdata = obj._vals_[attr] = SetData()
-        elif setdata.is_fully_loaded: return setdata
+        elif setdata.is_fully_loaded and not attr.is_volatile:
+            return setdata
         entity = attr.entity
         reverse = attr.reverse
         rentity = reverse.entity
@@ -2968,7 +2979,7 @@
                 else:
                     phantoms = setdata2 - items
                     if setdata2.added: phantoms -= setdata2.added
-                    if phantoms: throw(UnrepeatableReadError,
+                    if phantoms and not attr.is_volatile: 
throw(UnrepeatableReadError,
                         'Phantom object %s disappeared from collection %s.%s'
                         % (safe_repr(phantoms.pop()), safe_repr(obj2), 
attr.name))
                 items -= setdata2
@@ -3125,7 +3136,7 @@
         for obj in objects:
             setdata = obj._vals_.get(attr)
             if setdata is None: setdata = obj._vals_[attr] = SetData()
-            elif setdata.is_fully_loaded: throw(UnrepeatableReadError,
+            elif setdata.is_fully_loaded and not attr.is_volatile: 
throw(UnrepeatableReadError,
                 'Phantom object %s appeared in collection %s.%s' % 
(safe_repr(item), safe_repr(obj), attr.name))
             setdata.add(item)
     def reverse_remove(attr, objects, item, undo_funcs):
@@ -3569,7 +3580,7 @@
     def load(wrapper):
         wrapper._attr_.load(wrapper._obj_)
     @cut_traceback
-    def select(wrapper, *args):
+    def select(wrapper, *args, **kwargs):
         obj = wrapper._obj_
         if obj._status_ in del_statuses: throw_object_was_deleted(obj)
         attr = wrapper._attr_
@@ -3578,8 +3589,10 @@
         s = 'lambda item: JOIN(obj in item.%s)' if reverse.is_collection else 
'lambda item: item.%s == obj'
         query = query.filter(s % reverse.name, {'obj' : obj, 'JOIN': JOIN})
         if args:
-            func, globals, locals = get_globals_and_locals(args, kwargs=None, 
frame_depth=cut_traceback_depth+1)
+            func, globals, locals = get_globals_and_locals(args, kwargs, 
frame_depth=cut_traceback_depth+1)
             query = query.filter(func, globals, locals)
+        if kwargs:
+            query = query._apply_kwargs(kwargs)
         return query
     filter = select
     def limit(wrapper, limit=None, offset=None):
@@ -4017,8 +4030,12 @@
         assert len(objects) == 1
         return objects[0]
     @cut_traceback
-    def select(entity, *args):
-        return entity._query_from_args_(args, kwargs=None, 
frame_depth=cut_traceback_depth+1)
+    def select(entity, *args, **kwargs):
+        if args: query = entity._query_from_args_(args, kwargs, 
frame_depth=cut_traceback_depth+1)
+        else:
+            query = entity._select_all()
+            if kwargs: query = query._apply_kwargs(kwargs)
+        return query
     @cut_traceback
     def select_by_sql(entity, sql, globals=None, locals=None):
         return entity._find_by_sql_(None, sql, globals, locals, 
frame_depth=cut_traceback_depth+1)
@@ -4360,7 +4377,7 @@
     def _select_all(entity):
         return Query(entity._default_iter_name_, entity._default_genexpr_, {}, 
{ '.0' : entity })
     def _query_from_args_(entity, args, kwargs, frame_depth):
-        if not args and not kwargs: return entity._select_all()
+        assert args
         func, globals, locals = get_globals_and_locals(args, kwargs, 
frame_depth+1)
 
         if type(func) is types.FunctionType:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/dbproviders/postgres.py 
new/pony-0.7.14/pony/orm/dbproviders/postgres.py
--- old/pony-0.7.13/pony/orm/dbproviders/postgres.py    2020-03-03 
14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/orm/dbproviders/postgres.py    2020-11-23 
18:56:27.000000000 +0100
@@ -196,6 +196,10 @@
             pool.drop(con)
             raise
 
+
+ADMIN_SHUTDOWN = '57P01'
+
+
 class PGProvider(DBAPIProvider):
     dialect = 'PostgreSQL'
     paramstyle = 'pyformat'
@@ -222,7 +226,8 @@
         provider.table_if_not_exists_syntax = provider.server_version >= 90100
 
     def should_reconnect(provider, exc):
-        return isinstance(exc, psycopg2.OperationalError) and exc.pgcode is 
None
+        return isinstance(exc, psycopg2.OperationalError) \
+               and exc.pgcode in (None, ADMIN_SHUTDOWN)
 
     def get_pool(provider, *args, **kwargs):
         return PGPool(provider.dbapi_module, *args, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/dbproviders/sqlite.py 
new/pony-0.7.14/pony/orm/dbproviders/sqlite.py
--- old/pony-0.7.13/pony/orm/dbproviders/sqlite.py      2020-03-03 
14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/orm/dbproviders/sqlite.py      2020-11-23 
15:55:41.000000000 +0100
@@ -87,6 +87,22 @@
         if stop is None:
             stop = [ 'VALUE', None ]
         return "py_string_slice(", builder(expr), ', ', builder(start), ', ', 
builder(stop), ")"
+    def IN(builder, expr1, x):
+        if not x:
+            return '0 = 1'
+        if len(x) >= 1 and x[0] == 'SELECT':
+            return builder(expr1), ' IN ', builder(x)
+        op = ' IN (VALUES ' if expr1[0] == 'ROW' else ' IN ('
+        expr_list = [ builder(expr) for expr in x ]
+        return builder(expr1), op, join(', ', expr_list), ')'
+    def NOT_IN(builder, expr1, x):
+        if not x:
+            return '1 = 1'
+        if len(x) >= 1 and x[0] == 'SELECT':
+            return builder(expr1), ' NOT IN ', builder(x)
+        op = ' NOT IN (VALUES ' if expr1[0] == 'ROW' else ' NOT IN ('
+        expr_list = [ builder(expr) for expr in x ]
+        return builder(expr1), op, join(', ', expr_list), ')'
     def TODAY(builder):
         return "date('now', 'localtime')"
     def NOW(builder):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/decompiling.py 
new/pony-0.7.14/pony/orm/decompiling.py
--- old/pony-0.7.13/pony/orm/decompiling.py     2020-03-03 14:51:21.000000000 
+0100
+++ new/pony-0.7.14/pony/orm/decompiling.py     2020-11-23 09:45:16.000000000 
+0100
@@ -327,6 +327,9 @@
         oper1 = decompiler.stack.pop()
         return ast.Compare(oper1, [(op, oper2)])
 
+    def CONTAINS_OP(decompiler, invert):
+        return decompiler.COMPARE_OP('not in' if invert else 'in')
+
     def DUP_TOP(decompiler):
         return decompiler.stack[-1]
 
@@ -353,6 +356,9 @@
 
     JUMP_IF_FALSE_OR_POP = JUMP_IF_FALSE
 
+    def JUMP_IF_NOT_EXC_MATCH(decompiler, endpos):
+        raise NotImplementedError
+
     def JUMP_IF_TRUE(decompiler, endpos):
         return decompiler.conditional_jump(endpos, True)
 
@@ -436,10 +442,28 @@
         if decompiler.targets.get(endpos) is then: decompiler.targets[endpos] 
= if_exp
         return if_exp
 
+    def IS_OP(decompiler, invert):
+        return decompiler.COMPARE_OP('is not' if invert else 'is')
+
     def LIST_APPEND(decompiler, offset=None):
         throw(InvalidQuery('Use generator expression (... for ... in ...) '
                            'instead of list comprehension [... for ... in ...] 
inside query'))
 
+    def LIST_EXTEND(decompiler, offset):
+        if offset != 1:
+            raise NotImplementedError(offset)
+        items = decompiler.stack.pop()
+        if not isinstance(items, ast.Const):
+            raise NotImplementedError(type(items))
+        if not isinstance(items.value, tuple):
+            raise NotImplementedError(type(items.value))
+        lst = decompiler.stack.pop()
+        if not isinstance(lst, ast.List):
+            raise NotImplementedError(type(lst))
+        values = tuple(ast.Const(v) for v in items.value)
+        lst.nodes = lst.nodes + values
+        return lst
+
     def LOAD_ATTR(decompiler, attr_name):
         return ast.Getattr(decompiler.stack.pop(), attr_name)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pony-0.7.13/pony/orm/examples/test_cockroach_retry.py 
new/pony-0.7.14/pony/orm/examples/test_cockroach_retry.py
--- old/pony-0.7.13/pony/orm/examples/test_cockroach_retry.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/pony-0.7.14/pony/orm/examples/test_cockroach_retry.py   2020-01-28 
19:18:10.000000000 +0100
@@ -0,0 +1,29 @@
+from pony.orm import *
+
+db = Database(
+    provider='cockroach', user='root', host='localhost', port=26257, 
sslmode='disable', database='test'
+)
+
+class T1(db.Entity):
+    a = PrimaryKey(int)
+    b = Required(str)
+
+db.generate_mapping(check_tables=True)
+
+sql_debug(True)
+
+@db_session(retry=10)
+def f():
+    x = T1[10]
+    x.b += '#'
+    flush()
+    import time
+    for i in range(30):
+        print('((%d))' % i)
+        time.sleep(1)
+    y = T1[20]
+    y.b += '@'
+    flush()
+
+if __name__ == '__main__':
+    f()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/sqltranslation.py 
new/pony-0.7.14/pony/orm/sqltranslation.py
--- old/pony-0.7.13/pony/orm/sqltranslation.py  2020-03-03 14:51:21.000000000 
+0100
+++ new/pony-0.7.14/pony/orm/sqltranslation.py  2020-11-23 15:55:41.000000000 
+0100
@@ -221,11 +221,9 @@
         try:
             translator.init(tree, parent_translator, code_key, filter_num, 
extractors, vars, vartypes, left_join, optimize)
         except UseAnotherTranslator as e:
-            assert local.translators
-            t = local.translators.pop()
-            assert t is e.translator
+            translator = e.translator
             raise
-        else:
+        finally:
             assert local.translators
             t = local.translators.pop()
             assert t is translator
@@ -901,9 +899,8 @@
             namespace = None
         if namespace is not None:
             translator.namespace_stack.append(namespace)
-
-        with translator:
-            try:
+        try:
+            with translator:
                 translator.dispatch(func_ast)
                 if isinstance(func_ast, ast.Tuple): nodes = func_ast.nodes
                 else: nodes = (func_ast,)
@@ -911,8 +908,9 @@
                     translator.inside_order_by = True
                     new_order = []
                     for node in nodes:
-                        if isinstance(node.monad, SetMixin):
-                            t = node.monad.type.item_type
+                        monad = node.monad.to_single_cell_value()
+                        if isinstance(monad, SetMixin):
+                            t = monad.type.item_type
                             if isinstance(type(t), type): t = t.__name__
                             throw(TranslationError, 'Set of %s (%s) cannot be 
used for ordering'
                                                     % (t, ast2src(node)))
@@ -929,10 +927,10 @@
                             else: 
translator.having_conditions.extend(m.getsql())
                 translator.vars = None
                 return translator
-            finally:
-                if namespace is not None:
-                    ns = translator.namespace_stack.pop()
-                    assert ns is namespace
+        finally:
+            if namespace is not None:
+                ns = translator.namespace_stack.pop()
+                assert ns is namespace
     def preGenExpr(translator, node):
         inner_tree = node.code
         translator_cls = translator.__class__
@@ -970,10 +968,7 @@
         value = node.value
         if type(value) is frozenset:
             value = tuple(sorted(value))
-        if type(value) is not tuple:
-            return ConstMonad.new(value)
-        else:
-            return ListMonad([ ConstMonad.new(item) for item in value ])
+        return ConstMonad.new(value)
     def postEllipsis(translator, node):
         return ConstMonad.new(Ellipsis)
     def postList(translator, node):
@@ -1428,6 +1423,8 @@
         monad.mixin_init()
     def mixin_init(monad):
         pass
+    def to_single_cell_value(monad):
+        return monad
     def cmp(monad, op, monad2):
         return CmpMonad(op, monad, monad2)
     def contains(monad, item, not_in=False): throw(TypeError)
@@ -2363,7 +2360,9 @@
     @staticmethod
     def new(value):
         value_type, value = normalize(value)
-        if value_type in numeric_types: cls = NumericConstMonad
+        if isinstance(value_type, tuple):
+            return ListMonad([ConstMonad.new(item) for item in value])
+        elif value_type in numeric_types: cls = NumericConstMonad
         elif value_type is unicode: cls = StringConstMonad
         elif value_type is date: cls = DateConstMonad
         elif value_type is time: cls = TimeConstMonad
@@ -2569,9 +2568,9 @@
                 root_translator.vartypes.update(func_vartypes)
                 root_translator.vars.update(func_vars)
 
+        func_ast = copy_ast(func_ast)
         stack = translator.namespace_stack
         stack.append(name_mapping)
-        func_ast = copy_ast(func_ast)
         try:
             prev_code_key = translator.code_key
             translator.code_key = func_id
@@ -2584,7 +2583,8 @@
                 msg = e.args[0] + ' (inside %s)' % (monad.func_name)
                 e.args = (msg,)
             raise
-        stack.pop()
+        finally:
+            stack.pop()
         return func_ast.monad
 
 class HybridMethodMonad(HybridFuncMonad):
@@ -2840,11 +2840,16 @@
     func = coalesce
     def call(monad, *args):
         if len(args) < 2: throw(TranslationError, 'coalesce() function 
requires at least two arguments')
-        arg = args[0]
+        arg = args[0].to_single_cell_value()
         t = arg.type
         result = [ [ sql ] for sql in arg.getsql() ]
         for arg in args[1:]:
-            if arg.type is not t: throw(TypeError, 'All arguments of 
coalesce() function should have the same type')
+            arg = arg.to_single_cell_value()
+            if arg.type is not t:
+                t2 = coerce_types(t, arg.type)
+                if t2 is None:
+                    throw(TypeError, 'All arguments of coalesce() function 
should have the same type')
+                t = t2
             for i, sql in enumerate(arg.getsql()):
                 result[i].append(sql)
         sql = [ [ 'COALESCE' ] + coalesce_args for coalesce_args in result ]
@@ -3354,6 +3359,8 @@
         monad.subtranslator = subtranslator
         monad.item_type = item_type
         monad.limit = monad.offset = None
+    def to_single_cell_value(monad):
+        return ExprMonad.new(monad.item_type, monad.getsql()[0])
     def requires_distinct(monad, joined=False):
         assert False
     def call_limit(monad, limit=None, offset=None):
@@ -3561,7 +3568,7 @@
                 throw(TypeError, '`sep` option of `group_concat` should be 
type of str. Got: %s' % type(sep).__name__)
         return monad.aggregate('GROUP_CONCAT', distinct, sep=sep)
     def getsql(monad):
-        return monad.subtranslator.construct_subquery_ast(monad.limit, 
monad.offset)
+        return [ monad.subtranslator.construct_subquery_ast(monad.limit, 
monad.offset) ]
 
 def find_or_create_having_ast(sections):
     groupby_offset = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/pony_test_config.py 
new/pony-0.7.14/pony/orm/tests/pony_test_config.py
--- old/pony-0.7.13/pony/orm/tests/pony_test_config.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/pony_test_config.py  2020-11-23 
17:45:20.000000000 +0100
@@ -0,0 +1,5 @@
+settings = dict(
+    provider = 'sqlite', filename = ':memory:'
+    # provider='postgres', user='pony', password='pony', host='localhost', 
port='5432', database='pony',
+    # provider='cockroach', user='root', host='localhost', port=26257, 
sslmode='disable', database='test'
+)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/test_conversions.py 
new/pony-0.7.14/pony/orm/tests/test_conversions.py
--- old/pony-0.7.13/pony/orm/tests/test_conversions.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_conversions.py  2020-10-24 
14:49:39.000000000 +0200
@@ -0,0 +1,67 @@
+import unittest
+from datetime import timedelta
+
+from pony import converting
+
+class Test(unittest.TestCase):
+    def test_timestamps_1(self):
+        s = '1:2:3'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(hours=1, minutes=2, seconds=3))
+        s = '01:02:03'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(hours=1, minutes=2, seconds=3))
+        s = '1:2:3.456'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(hours=1, minutes=2, seconds=3, 
milliseconds=456))
+        s = '1:2:3.45678'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(hours=1, minutes=2, seconds=3, 
microseconds=456780))
+        s = '12:34:56'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(hours=12, minutes=34, seconds=56))
+        s = '12:34:56.789'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(hours=12, minutes=34, seconds=56, 
milliseconds=789))
+        s = '12:34:56.789123'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(hours=12, minutes=34, seconds=56, 
microseconds=789123))
+
+    def test_timestamps_2(self):
+        s = '-1:2:3'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(hours=1, minutes=2, seconds=3))
+        s = '-01:02:03'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(hours=1, minutes=2, seconds=3))
+        s = '-1:2:3.456'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(hours=1, minutes=2, seconds=3, 
milliseconds=456))
+        s = '-1:2:3.45678'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(hours=1, minutes=2, seconds=3, 
microseconds=456780))
+        s = '-12:34:56'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(hours=12, minutes=34, seconds=56))
+        s = '-12:34:56.789'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(hours=12, minutes=34, seconds=56, 
milliseconds=789))
+        s = '-12:34:56.789123'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(hours=12, minutes=34, seconds=56, 
microseconds=789123))
+
+    def test_timestamps_3(self):
+        s = '0:1:2'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(minutes=1, seconds=2))
+        s = '0:1:2.3456'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, timedelta(minutes=1, seconds=2, 
microseconds=345600))
+
+    def test_timestamps_4(self):
+        s = '-0:1:2'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(minutes=1, seconds=2))
+        s = '-0:1:2.3456'
+        td = converting.str2timedelta(s)
+        self.assertEqual(td, -timedelta(minutes=1, seconds=2, 
microseconds=345600))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/pony-0.7.13/pony/orm/tests/test_declarative_exceptions.py 
new/pony-0.7.14/pony/orm/tests/test_declarative_exceptions.py
--- old/pony-0.7.13/pony/orm/tests/test_declarative_exceptions.py       
2020-03-03 14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_declarative_exceptions.py       
2020-11-23 09:45:16.000000000 +0100
@@ -155,9 +155,12 @@
     @raises_exception(NotImplementedError, "date(s.id, 1, 1)")
     def test30(self):
         select(s for s in Student if s.dob < date(s.id, 1, 1))
-    @raises_exception(ExprEvalError, "`max()` raises TypeError: max() expects 
at least one argument" if PYPY else
-                                     "`max()` raises TypeError: max expected 1 
arguments, got 0" if sys.version_info[:2] < (3, 8) else
-                                     "`max()` raises TypeError: max expected 1 
argument, got 0")
+    @raises_exception(
+        ExprEvalError,
+        "`max()` raises TypeError: max() expects at least one argument" if 
PYPY else
+        "`max()` raises TypeError: max expected 1 arguments, got 0" if 
sys.version_info[:2] < (3, 8) else
+        "`max()` raises TypeError: max expected 1 argument, got 0" if 
sys.version_info[:2] < (3, 9) else
+        "`max()` raises TypeError: max expected at least 1 argument, got 0")
     def test31(self):
         select(s for s in Student if s.id < max())
     @raises_exception(TypeError, "Incomparable types 'Student' and 'Course' in 
expression: s in s.courses")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/test_diagram_keys.py 
new/pony-0.7.14/pony/orm/tests/test_diagram_keys.py
--- old/pony-0.7.13/pony/orm/tests/test_diagram_keys.py 2020-03-03 
14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_diagram_keys.py 2020-10-24 
14:49:39.000000000 +0200
@@ -159,13 +159,6 @@
         class Entity1(db.Entity):
             a = PrimaryKey(int, volatile=True)
 
-    @raises_exception(TypeError, 'Set attribute Entity1.b cannot be volatile')
-    def test_volatile_set(self):
-        db = self.db = Database(**db_params)
-        class Entity1(db.Entity):
-            a = PrimaryKey(int)
-            b = Set('Entity2', volatile=True)
-
     @raises_exception(TypeError, 'Volatile attribute Entity1.b cannot be part 
of primary key')
     def test_volatile_composite_pk(self):
         db = self.db = Database(**db_params)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/test_hooks.py 
new/pony-0.7.14/pony/orm/tests/test_hooks.py
--- old/pony-0.7.13/pony/orm/tests/test_hooks.py        2020-03-03 
14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_hooks.py        2020-10-24 
14:49:39.000000000 +0200
@@ -57,6 +57,11 @@
 set_hooks_to_do_nothing()
 
 
+def flush_for(*objects):
+    for obj in objects:
+        obj.flush()
+
+
 class TestHooks(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
@@ -79,7 +84,7 @@
         pass
 
     @db_session
-    def test_1(self):
+    def test_1a(self):
         p4 = Person(id=4, name='Bob', age=16)
         p5 = Person(id=5, name='Lucy', age=23)
         self.assertEqual(logged_events, [])
@@ -87,7 +92,15 @@
         self.assertEqual(logged_events, ['BI_Bob', 'BI_Lucy', 'AI_Bob', 
'AI_Lucy'])
 
     @db_session
-    def test_2(self):
+    def test_1b(self):
+        p4 = Person(id=4, name='Bob', age=16)
+        p5 = Person(id=5, name='Lucy', age=23)
+        self.assertEqual(logged_events, [])
+        flush_for(p4, p5)
+        self.assertEqual(logged_events, ['BI_Bob', 'AI_Bob', 'BI_Lucy', 
'AI_Lucy'])
+
+    @db_session
+    def test_2a(self):
         p4 = Person(id=4, name='Bob', age=16)
         p1 = Person[1]  # auto-flush here
         p2 = Person[2]
@@ -98,50 +111,7 @@
         self.assertEqual(logged_events, ['BI_Bob', 'AI_Bob', 'BU_Mary', 
'BI_Lucy', 'AU_Mary', 'AI_Lucy'])
 
     @db_session
-    def test_3(self):
-        global do_before_insert
-        def do_before_insert(person):
-            some_person = Person.select().first()  # should not cause infinite 
recursion
-        p4 = Person(id=4, name='Bob', age=16)
-        db.flush()
-
-
-def flush_for(*objects):
-    for obj in objects:
-        obj.flush()
-
-
-class ObjectFlushTest(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls):
-        setup_database(db)
-
-    @classmethod
-    def tearDownClass(cls):
-        teardown_database(db)
-
-    def setUp(self):
-        set_hooks_to_do_nothing()
-        with db_session:
-            db.execute('delete from Person')
-            p1 = Person(id=1, name='John', age=22)
-            p2 = Person(id=2, name='Mary', age=18)
-            p3 = Person(id=3, name='Mike', age=25)
-        logged_events[:] = []
-
-    def tearDown(self):
-        pass
-
-    @db_session
-    def test_1(self):
-        p4 = Person(id=4, name='Bob', age=16)
-        p5 = Person(id=5, name='Lucy', age=23)
-        self.assertEqual(logged_events, [])
-        flush_for(p4, p5)
-        self.assertEqual(logged_events, ['BI_Bob', 'AI_Bob', 'BI_Lucy', 
'AI_Lucy'])
-
-    @db_session
-    def test_2(self):
+    def test_2b(self):
         p4 = Person(id=4, name='Bob', age=16)
         p1 = Person[1]  # auto-flush here
         p2 = Person[2]
@@ -157,7 +127,15 @@
         def do_before_insert(person):
             some_person = Person.select().first()  # should not cause infinite 
recursion
         p4 = Person(id=4, name='Bob', age=16)
-        p4.flush()
+        db.flush()
+
+    @db_session
+    def test_4(self):
+        global do_before_insert
+        def do_before_insert(person):
+            some_person = Person.select().first()  # creates nested 
prefetch_context
+        p4 = Person(id=4, name='Bob', age=16)
+        Person.select().first()
 
 
 if __name__ == '__main__':
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/test_list_monad.py 
new/pony-0.7.14/pony/orm/tests/test_list_monad.py
--- old/pony-0.7.13/pony/orm/tests/test_list_monad.py   1970-01-01 
01:00:00.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_list_monad.py   2020-11-23 
15:55:41.000000000 +0100
@@ -0,0 +1,151 @@
+from __future__ import absolute_import, print_function, division
+
+import unittest
+from datetime import date
+
+from pony.orm.core import *
+from pony.orm.tests.testutils import *
+from pony.orm.tests import setup_database, teardown_database
+
+db = Database()
+
+class Department(db.Entity):
+    number = PrimaryKey(int, auto=True)
+    name = Required(unicode, unique=True)
+    groups = Set("Group")
+    courses = Set("Course")
+
+class Group(db.Entity):
+    number = PrimaryKey(int)
+    major = Required(unicode)
+    dept = Required("Department")
+    students = Set("Student")
+
+class Course(db.Entity):
+    name = Required(unicode)
+    semester = Required(int)
+    lect_hours = Required(int)
+    lab_hours = Required(int)
+    credits = Required(int)
+    dept = Required(Department)
+    students = Set("Student")
+    PrimaryKey(name, semester)
+
+class Student(db.Entity):
+    id = PrimaryKey(int, auto=True)
+    name = Required(unicode)
+    dob = Required(date)
+    tel = Optional(str)
+    picture = Optional(buffer, lazy=True)
+    gpa = Required(float, default=0)
+    phd = Optional(bool)
+    group = Required(Group)
+    courses = Set(Course)
+
+class TestListMonad(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        setup_database(db)
+        with db_session:
+            d1 = Department(number=1, name="Department of Computer Science")
+            d2 = Department(number=2, name="Department of Mathematical 
Sciences")
+            d3 = Department(number=3, name="Department of Applied Physics")
+
+            c1 = Course(name="Web Design", semester=1, dept=d1,
+                        lect_hours=30, lab_hours=30, credits=3)
+            c2 = Course(name="Data Structures and Algorithms", semester=3, 
dept=d1,
+                        lect_hours=40, lab_hours=20, credits=4)
+
+            c3 = Course(name="Linear Algebra", semester=1, dept=d2,
+                        lect_hours=30, lab_hours=30, credits=4)
+            c4 = Course(name="Statistical Methods", semester=2, dept=d2,
+                        lect_hours=50, lab_hours=25, credits=5)
+
+            c5 = Course(name="Thermodynamics", semester=2, dept=d3,
+                        lect_hours=25, lab_hours=40, credits=4)
+            c6 = Course(name="Quantum Mechanics", semester=3, dept=d3,
+                        lect_hours=40, lab_hours=30, credits=5)
+
+            g101 = Group(number=101, major='B.E. in Computer Engineering', 
dept=d1)
+            g102 = Group(number=102, major='B.S./M.S. in Computer Science', 
dept=d2)
+            g103 = Group(number=103, major='B.S. in Applied Mathematics and 
Statistics', dept=d2)
+            g104 = Group(number=104, major='B.S./M.S. in Pure Mathematics', 
dept=d2)
+            g105 = Group(number=105, major='B.E in Electronics', dept=d3)
+            g106 = Group(number=106, major='B.S./M.S. in Nuclear Engineering', 
dept=d3)
+
+            Student(id=1, name='John Smith', dob=date(1991, 3, 20), 
tel='123-456', gpa=3, group=g101, phd=True,
+                    courses=[c1, c2, c4, c6])
+            Student(id=2, name='Matthew Reed', dob=date(1990, 11, 26), 
gpa=3.5, group=g101, phd=True,
+                    courses=[c1, c3, c4, c5])
+            Student(id=3, name='Chuan Qin', dob=date(1989, 2, 5), gpa=4, 
group=g101,
+                    courses=[c3, c5, c6])
+            Student(id=4, name='Rebecca Lawson', dob=date(1990, 4, 18), 
tel='234-567', gpa=3.3, group=g102,
+                    courses=[c1, c4, c5, c6])
+            Student(id=5, name='Maria Ionescu', dob=date(1991, 4, 23), 
gpa=3.9, group=g102,
+                    courses=[c1, c2, c4, c6])
+            Student(id=6, name='Oliver Blakey', dob=date(1990, 9, 8), gpa=3.1, 
group=g102,
+                    courses=[c1, c2, c5])
+            Student(id=7, name='Jing Xia', dob=date(1988, 12, 30), gpa=3.2, 
group=g102,
+                    courses=[c1, c3, c5, c6])
+
+    @classmethod
+    def tearDownClass(cls):
+        teardown_database(db)
+
+    def setUp(self):
+        rollback()
+        db_session.__enter__()
+
+    def tearDown(self):
+        rollback()
+        db_session.__exit__()
+
+    def test_in_simple(self):
+        q = select(s.id for s in Student if s.name in ('John Smith', 'Matthew 
Reed'))
+        self.assertEqual(set(q), {1, 2})
+
+    def test_not_in_simple(self):
+        q = select(s.id for s in Student if s.name not in ('John Smith', 
'Matthew Reed'))
+        self.assertEqual(set(q), {3, 4, 5, 6, 7})
+
+    def test_in_composite(self):
+        q = select(c.name for c in Course if (c.name, c.semester) in [
+            ("Web Design", 1), ("Thermodynamics", 2), ("Theology", 3)
+        ])
+        self.assertEqual(set(q), {"Web Design", "Thermodynamics"})
+
+    def test_not_in_composite(self):
+        q = select(c.name for c in Course if (c.name, c.semester) not in [
+            ("Web Design", 1), ("Thermodynamics", 2), ("Theology", 3)
+        ])
+        self.assertEqual(set(q), {
+            "Data Structures and Algorithms", "Linear Algebra",
+            "Statistical Methods", "Quantum Mechanics"
+        })
+
+    def test_in_simple_object(self):
+        s1, s2 = Student[1], Student[2]
+        q = select(s.id for s in Student if s in (s1, s2))
+        self.assertEqual(set(q), {1, 2})
+
+    def test_not_in_simple_object(self):
+        s1, s2 = Student[1], Student[2]
+        q = select(s.id for s in Student if s not in (s1, s2))
+        self.assertEqual(set(q), {3, 4, 5, 6, 7})
+
+    def test_in_composite_object(self):
+        c1, c2 = Course["Web Design", 1], Course["Thermodynamics", 2]
+        q = select(c.name for c in Course if c in (c1, c2))
+        self.assertEqual(set(q), {"Web Design", "Thermodynamics"})
+
+    def test_not_in_composite_object(self):
+        c1, c2 = Course["Web Design", 1], Course["Thermodynamics", 2]
+        q = select(c.name for c in Course if c not in (c1, c2))
+        self.assertEqual(set(q), {
+            "Data Structures and Algorithms", "Linear Algebra",
+            "Statistical Methods", "Quantum Mechanics"
+        })
+
+
+if __name__ == "__main__":
+    unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/test_prop_sum_orderby.py 
new/pony-0.7.14/pony/orm/tests/test_prop_sum_orderby.py
--- old/pony-0.7.13/pony/orm/tests/test_prop_sum_orderby.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_prop_sum_orderby.py     2020-10-24 
14:49:39.000000000 +0200
@@ -0,0 +1,181 @@
+from __future__ import absolute_import, print_function, division
+
+import unittest
+
+from pony.orm.core import *
+from pony.orm.tests.testutils import *
+from pony.orm.tests import setup_database, teardown_database
+
+db = Database()
+
+db = Database('sqlite', ':memory:')
+
+class Product(db.Entity):
+    id = PrimaryKey(int)
+    name = Required(str)
+    comments = Set('Comment')
+
+    @property
+    def sum_01(self):
+        return coalesce(select(c.points for c in self.comments).sum(), 0)
+
+    @property
+    def sum_02(self):
+        return coalesce(select(c.points for c in self.comments).sum(), 0.0)
+
+    @property
+    def sum_03(self):
+        return coalesce(select(sum(c.points) for c in self.comments), 0)
+
+    @property
+    def sum_04(self):
+        return coalesce(select(sum(c.points) for c in self.comments), 0.0)
+
+    @property
+    def sum_05(self):
+        return sum(c.points for c in self.comments)
+
+    @property
+    def sum_06(self):
+        return coalesce(sum(c.points for c in self.comments), 0)
+
+    @property
+    def sum_07(self):
+        return coalesce(sum(c.points for c in self.comments), 0.0)
+
+    @property
+    def sum_08(self):
+        return select(sum(c.points) for c in self.comments)
+
+    @property
+    def sum_09(self):
+        return select(coalesce(sum(c.points), 0) for c in self.comments)
+
+    @property
+    def sum_10(self):
+        return select(coalesce(sum(c.points), 0.0) for c in self.comments)
+
+    @property
+    def sum_11(self):
+        return select(sum(c.points) for c in self.comments)
+
+    @property
+    def sum_12(self):
+        return sum(self.comments.points)
+
+    @property
+    def sum_13(self):
+        return coalesce(sum(self.comments.points), 0)
+
+    @property
+    def sum_14(self):
+        return coalesce(sum(self.comments.points), 0.0)
+
+
+class Comment(db.Entity):
+    id = PrimaryKey(int)
+    points = Required(int)
+    product = Optional('Product')
+
+
+class TestQuerySetMonad(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        setup_database(db)
+        with db_session:
+            p1 = Product(id=1, name='P1')
+            p2 = Product(id=2, name='P1', comments=[
+                Comment(id=201, points=5)
+            ])
+            p3 = Product(id=3, name='P1', comments=[
+                Comment(id=301, points=1), Comment(id=302, points=2)
+            ])
+            p4 = Product(id=4, name='P1', comments=[
+                Comment(id=401, points=1), Comment(id=402, points=5), 
Comment(id=403, points=1)
+            ])
+
+    @classmethod
+    def tearDownClass(cls):
+        teardown_database(db)
+
+    def setUp(self):
+        rollback()
+        db_session.__enter__()
+
+    def tearDown(self):
+        rollback()
+        db_session.__exit__()
+
+    def test_sum_01(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_01))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_02(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_02))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_03(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_03))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_04(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_04))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_05(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_05))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_06(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_06))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_07(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_07))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_08(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_08))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_09(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_09))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_10(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_10))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_11(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_11))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_12(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_12))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_13(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_13))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+    def test_sum_14(self):
+        q = list(Product.select().sort_by(lambda p: p.sum_14))
+        result = [p.id for p in q]
+        self.assertEqual(result, [1, 3, 2, 4])
+
+
+if __name__ == "__main__":
+    unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/test_query.py 
new/pony-0.7.14/pony/orm/tests/test_query.py
--- old/pony-0.7.13/pony/orm/tests/test_query.py        2020-03-03 
14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_query.py        2020-10-24 
14:49:39.000000000 +0200
@@ -128,6 +128,34 @@
     def test23(self):
         r = max(s.dob.year for s in Student)
         self.assertEqual(r, 2001)
+    def test_select_kwargs_1(self):
+        r = Student.select(scholarship=200)[:]
+        self.assertEqual(r, [Student[3]])
+    def test_select_kwargs_1a(self):
+        g = Group[1]
+        r = g.students.select(scholarship=200)[:]
+        self.assertEqual(r, [Student[3]])
+    def test_select_kwargs_2(self):
+        r = Student.select(scholarship=1000)[:]
+        self.assertEqual(r, [])
+    def test_select_kwargs_2a(self):
+        g = Group[1]
+        r = g.students.select(scholarship=1000)[:]
+        self.assertEqual(r, [])
+    def test_select_kwargs_3(self):
+        r = Student.select(group=Group[1])[:]
+        self.assertEqual(set(r), {Student[1], Student[2], Student[3]})
+    def test_select_kwargs_3a(self):
+        g = Group[1]
+        r = g.students.select(group=g)[:]
+        self.assertEqual(set(r), {Student[1], Student[2], Student[3]})
+    def test_select_kwargs_4(self):
+        r = Student.select(group=Group[1], scholarship=200)[:]
+        self.assertEqual(r, [Student[3]])
+    def test_select_kwargs_4a(self):
+        g = Group[1]
+        r = g.students.select(group=g, scholarship=200)[:]
+        self.assertEqual(r, [Student[3]])
     def test_first1(self):
         q = select(s for s in Student).order_by(Student.gpa)
         self.assertEqual(q.first(), Student[1])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/test_seeds.py 
new/pony-0.7.14/pony/orm/tests/test_seeds.py
--- old/pony-0.7.13/pony/orm/tests/test_seeds.py        1970-01-01 
01:00:00.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_seeds.py        2020-11-23 
18:56:27.000000000 +0100
@@ -0,0 +1,55 @@
+
+from __future__ import absolute_import, print_function, division
+from pony.py23compat import PYPY2, pickle
+
+import unittest
+from datetime import date
+from decimal import Decimal
+
+from pony.orm.core import *
+from pony.orm.tests.testutils import *
+from pony.orm.tests import teardown_database, setup_database
+
+db = Database()
+
+
+class Group(db.Entity):
+    id = PrimaryKey(int)
+    number = Required(str, unique=True)
+    students = Set("Student")
+
+
+class Student(db.Entity):
+    id = PrimaryKey(int)
+    name = Required(str)
+    group = Required("Group")
+
+
+class TestCRUD(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        setup_database(db)
+        with db_session:
+            g1 = Group(id=1, number='g111')
+            g2 = Group(id=2, number='g222')
+            s1 = Student(id=1, name='S1', group=g1)
+            s2 = Student(id=2, name='S2', group=g1)
+            s3 = Student(id=3, name='S3', group=g2)
+
+    @classmethod
+    def tearDownClass(cls):
+        teardown_database(db)
+
+    def setUp(self):
+        rollback()
+        db_session.__enter__()
+
+    def tearDown(self):
+        rollback()
+        db_session.__exit__()
+
+    def test_unique_load(self):
+        s1 = Student[1]
+        g1 = s1.group
+        g1.number = 'g123'
+        self.assertEqual(g1.number, 'g123')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony/orm/tests/test_volatile.py 
new/pony-0.7.14/pony/orm/tests/test_volatile.py
--- old/pony-0.7.13/pony/orm/tests/test_volatile.py     2020-03-03 
14:51:21.000000000 +0100
+++ new/pony-0.7.14/pony/orm/tests/test_volatile.py     2020-10-24 
14:49:39.000000000 +0200
@@ -5,7 +5,7 @@
 from pony.orm.tests import setup_database, teardown_database
 
 
-class TestVolatile(unittest.TestCase):
+class TestVolatile1(unittest.TestCase):
     def setUp(self):
         db = self.db = Database()
 
@@ -48,5 +48,80 @@
         item.flush()
         self.assertEqual(item.index, 1)
 
+
+class TestVolatile2(unittest.TestCase):
+    def setUp(self):
+        db = self.db = Database()
+
+        class Group(db.Entity):
+            number = PrimaryKey(int)
+            students = Set('Student', volatile=True)
+
+        class Student(db.Entity):
+            id = PrimaryKey(int)
+            name = Required(str)
+            group = Required('Group')
+            courses = Set('Course')
+
+        class Course(db.Entity):
+            id = PrimaryKey(int)
+            name = Required(str)
+            students = Set('Student', volatile=True)
+
+        setup_database(db)
+
+        with db_session:
+            g1 = Group(number=123)
+            s1 = Student(id=1, name='A', group=g1)
+            s2 = Student(id=2, name='B', group=g1)
+            c1 = Course(id=1, name='C1', students=[s1, s2])
+            c2 = Course(id=2, name='C1', students=[s1])
+
+        self.Group = Group
+        self.Student = Student
+        self.Course = Course
+
+    def tearDown(self):
+        teardown_database(self.db)
+
+    def test_1(self):
+        self.assertTrue(self.Group.students.is_volatile)
+        self.assertTrue(self.Student.group.is_volatile)
+        self.assertTrue(self.Student.courses.is_volatile)
+        self.assertTrue(self.Course.students.is_volatile)
+
+    def test_2(self):
+        with db_session:
+            g1 = self.Group[123]
+            students = set(s.id for s in g1.students)
+            self.assertEqual(students, {1, 2})
+            self.db.execute('''insert into student values(3, 'C', 123)''')
+            g1.students.load()
+            students = set(s.id for s in g1.students)
+            self.assertEqual(students, {1, 2, 3})
+
+    def test_3(self):
+        with db_session:
+            g1 = self.Group[123]
+            students = set(s.id for s in g1.students)
+            self.assertEqual(students, {1, 2})
+            self.db.execute("insert into student values(3, 'C', 123)")
+            s3 = self.Student[3]
+            students = set(s.id for s in g1.students)
+            self.assertEqual(students, {1, 2, 3})
+
+    def test_4(self):
+        with db_session:
+            c1 = self.Course[1]
+            students = set(s.id for s in c1.students)
+            self.assertEqual(students, {1, 2})
+            self.db.execute("insert into student values(3, 'C', 123)")
+            attr = self.Student.courses
+            self.db.execute("insert into %s values(1, 3)" % attr.table)
+            c1.students.load()
+            students = set(s.id for s in c1.students)
+            self.assertEqual(students, {1, 2, 3})
+
+
 if __name__ == '__main__':
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony.egg-info/PKG-INFO 
new/pony-0.7.14/pony.egg-info/PKG-INFO
--- old/pony-0.7.13/pony.egg-info/PKG-INFO      2020-03-03 14:51:21.000000000 
+0100
+++ new/pony-0.7.14/pony.egg-info/PKG-INFO      2020-11-23 19:53:36.000000000 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: pony
-Version: 0.7.13
+Version: 0.7.14
 Summary: Pony Object-Relational Mapper
 Home-page: https://ponyorm.com
 Author: Alexander Kozlovsky, Alexey Malashkevich
@@ -66,6 +66,7 @@
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
 Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Software Development :: Libraries
 Classifier: Topic :: Database
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/pony.egg-info/SOURCES.txt 
new/pony-0.7.14/pony.egg-info/SOURCES.txt
--- old/pony-0.7.13/pony.egg-info/SOURCES.txt   2020-03-03 14:51:21.000000000 
+0100
+++ new/pony-0.7.14/pony.egg-info/SOURCES.txt   2020-11-23 19:53:36.000000000 
+0100
@@ -43,6 +43,7 @@
 pony/orm/examples/demo.py
 pony/orm/examples/estore.py
 pony/orm/examples/inheritance1.py
+pony/orm/examples/test_cockroach_retry.py
 pony/orm/examples/test_numbers.py
 pony/orm/examples/university1.py
 pony/orm/examples/university2.py
@@ -51,6 +52,7 @@
 pony/orm/tests/__init__.py
 pony/orm/tests/fixtures.py
 pony/orm/tests/model1.py
+pony/orm/tests/pony_test_config.py
 pony/orm/tests/py36_test_f_strings.py
 pony/orm/tests/queries.txt
 pony/orm/tests/sql_tests.py
@@ -65,6 +67,7 @@
 pony/orm/tests/test_cascade.py
 pony/orm/tests/test_cascade_delete.py
 pony/orm/tests/test_collections.py
+pony/orm/tests/test_conversions.py
 pony/orm/tests/test_core_find_in_cache.py
 pony/orm/tests/test_core_multiset.py
 pony/orm/tests/test_crud.py
@@ -106,9 +109,11 @@
 pony/orm/tests/test_isinstance.py
 pony/orm/tests/test_json.py
 pony/orm/tests/test_lazy.py
+pony/orm/tests/test_list_monad.py
 pony/orm/tests/test_mapping.py
 pony/orm/tests/test_objects_to_save_cleanup.py
 pony/orm/tests/test_prefetching.py
+pony/orm/tests/test_prop_sum_orderby.py
 pony/orm/tests/test_query.py
 pony/orm/tests/test_random.py
 pony/orm/tests/test_raw_sql.py
@@ -120,6 +125,7 @@
 pony/orm/tests/test_relations_one2one4.py
 pony/orm/tests/test_relations_symmetric_m2m.py
 pony/orm/tests/test_relations_symmetric_one2one.py
+pony/orm/tests/test_seeds.py
 pony/orm/tests/test_select_from_select_queries.py
 pony/orm/tests/test_show.py
 pony/orm/tests/test_sqlbuilding_formatstyles.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pony-0.7.13/setup.py new/pony-0.7.14/setup.py
--- old/pony-0.7.13/setup.py    2020-03-03 14:51:21.000000000 +0100
+++ new/pony-0.7.14/setup.py    2020-11-23 09:45:16.000000000 +0100
@@ -73,6 +73,7 @@
     'Programming Language :: Python :: 3.6',
     'Programming Language :: Python :: 3.7',
     'Programming Language :: Python :: 3.8',
+    'Programming Language :: Python :: 3.9',
     'Programming Language :: Python :: Implementation :: PyPy',
     'Topic :: Software Development :: Libraries',
     'Topic :: Database'
@@ -106,8 +107,8 @@
 
 if __name__ == "__main__":
     pv = sys.version_info[:2]
-    if pv not in ((2, 7), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8)):
-        s = "Sorry, but %s %s requires Python of one of the following 
versions: 2.7, 3.3-3.8." \
+    if pv not in ((2, 7), (3, 3), (3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 
9)):
+        s = "Sorry, but %s %s requires Python of one of the following 
versions: 2.7, 3.3-3.9." \
             " You have version %s"
         print(s % (name, version, sys.version.split(' ', 1)[0]))
         sys.exit(1)

Reply via email to