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 [](https://pepy.tech/project/pony) [](https://pepy.tech/project/pony/month) [](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)