Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-peewee for openSUSE:Factory checked in at 2024-09-02 13:14:16 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-peewee (Old) and /work/SRC/openSUSE:Factory/.python-peewee.new.2698 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-peewee" Mon Sep 2 13:14:16 2024 rev:29 rq:1198059 version:3.17.6 Changes: -------- --- /work/SRC/openSUSE:Factory/python-peewee/python-peewee.changes 2024-06-07 15:04:47.022869894 +0200 +++ /work/SRC/openSUSE:Factory/.python-peewee.new.2698/python-peewee.changes 2024-09-02 13:14:22.766175183 +0200 @@ -1,0 +2,19 @@ +Sat Aug 31 12:12:35 UTC 2024 - Dirk Müller <dmuel...@suse.com> + +- update to 3.17.6: + * Fix bug in recursive `model.delete_instance()` when a table + contains foreign-keys at multiple depths of the graph + * Fix regression in pool behavior on systems where + `time.time()` returns identical values for two connections. + This adds a no-op comparable sentinel to the heap to prevent + any recurrence of this problem. + * Ensure that subqueries inside `CASE` statements generate + correct SQL. + * Fix regression that broke server-side cursors with Postgres + * Fix to ensure compatibility with psycopg3 - the libpq + TransactionStatus constants are no longer available on the + `Connection` instance. + * Fix quoting issue in pwiz that could generate invalid python + code for double-quoted string literals used as column defaults. + +------------------------------------------------------------------- Old: ---- peewee-3.17.5.tar.gz New: ---- peewee-3.17.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-peewee.spec ++++++ --- /var/tmp/diff_new_pack.p7x7lV/_old 2024-09-02 13:14:24.310239394 +0200 +++ /var/tmp/diff_new_pack.p7x7lV/_new 2024-09-02 13:14:24.318239726 +0200 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-peewee -Version: 3.17.5 +Version: 3.17.6 Release: 0 Summary: An expressive ORM that supports multiple SQL backends License: BSD-3-Clause ++++++ peewee-3.17.5.tar.gz -> peewee-3.17.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/CHANGELOG.md new/peewee-3.17.6/CHANGELOG.md --- old/peewee-3.17.5/CHANGELOG.md 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/CHANGELOG.md 2024-07-06 19:11:45.000000000 +0200 @@ -7,7 +7,30 @@ ## master -[View commits](https://github.com/coleifer/peewee/compare/3.17.4...master) +[View commits](https://github.com/coleifer/peewee/compare/3.17.6...master) + +## 3.17.6 + +* Fix bug in recursive `model.delete_instance()` when a table contains + foreign-keys at multiple depths of the graph, #2893. +* Fix regression in pool behavior on systems where `time.time()` returns + identical values for two connections. This adds a no-op comparable sentinel + to the heap to prevent any recurrence of this problem, #2901. +* Ensure that subqueries inside `CASE` statements generate correct SQL. +* Fix regression that broke server-side cursors with Postgres (introduced in + 3.16.0). +* Fix to ensure compatibility with psycopg3 - the libpq TransactionStatus + constants are no longer available on the `Connection` instance. +* Fix quoting issue in pwiz that could generate invalid python code for + double-quoted string literals used as column defaults. + +[View commits](https://github.com/coleifer/peewee/compare/3.17.5...3.17.6) + +## 3.17.5 + +This release fixes a build system problem in Python 3.12, #2891. + +[View commits](https://github.com/coleifer/peewee/compare/3.17.4...3.17.5) ## 3.17.4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/docs/peewee/api.rst new/peewee-3.17.6/docs/peewee/api.rst --- old/peewee-3.17.5/docs/peewee/api.rst 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/docs/peewee/api.rst 2024-07-06 19:11:45.000000000 +0200 @@ -3982,7 +3982,37 @@ .. py:class:: SubclassAwareMetadata - Metadata subclass that tracks :py:class:`Model` subclasses. + Metadata subclass that tracks :py:class:`Model` subclasses. Useful for + when you need to track all models in a project. + + Example: + + .. code-block:: python + + from peewee import SubclassAwareMetadata + + class Base(Model): + class Meta: + database = db + model_metadata_class = SubclassAwareMetadata + + # Create 3 model classes that inherit from Base. + class A(Base): pass + class B(Base): pass + class C(Base): pass + + # Now let's make a helper for changing the `schema` for each Model. + def change_schema(schema): + def _update(model): + model._meta.schema = schema + return _update + + # Set all models to use "schema1", e.g. "schema1.a", "schema1.b", etc. + # Will apply the function to every subclass of Base. + Base._meta.map_models(change_schema('schema1')) + + # Set all models to use "schema2", e.g. "schema2.a", "schema2.b", etc. + Base._meta.map_models(change_schema('schema2')) .. py:method:: map_models(fn) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/docs/peewee/query_operators.rst new/peewee-3.17.6/docs/peewee/query_operators.rst --- old/peewee-3.17.5/docs/peewee/query_operators.rst 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/docs/peewee/query_operators.rst 2024-07-06 19:11:45.000000000 +0200 @@ -117,7 +117,7 @@ * Use ``|`` instead of ``or`` * Use ``~`` instead of ``not`` * Use ``.is_null()`` instead of ``is None`` or ``== None``. - * Use ``== `` and ``!=`` for comparing against ``True`` and ``False``, or + * Use ``==`` and ``!=`` for comparing against ``True`` and ``False``, or you may use the implicit value of the expression. * **Don't forget to wrap your comparisons in parentheses when using logical operators.** diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/peewee.py new/peewee-3.17.6/peewee.py --- old/peewee-3.17.5/peewee.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/peewee.py 2024-07-06 19:11:45.000000000 +0200 @@ -74,7 +74,7 @@ mysql = None -__version__ = '3.17.5' +__version__ = '3.17.6' __all__ = [ 'AnyField', 'AsIs', @@ -1827,6 +1827,16 @@ return ctx.literal(self.window._alias or 'w') +class _InFunction(Node): + def __init__(self, node, in_function=True): + self.node = node + self.in_function = in_function + + def __sql__(self, ctx): + with ctx(in_function=self.in_function): + return ctx.sql(self.node) + + class Case(ColumnBase): def __init__(self, predicate, expression_tuples, default=None): self.predicate = predicate @@ -1838,9 +1848,10 @@ if self.predicate is not None: clauses.append(self.predicate) for expr, value in self.expression_tuples: - clauses.extend((SQL('WHEN'), expr, SQL('THEN'), value)) + clauses.extend((SQL('WHEN'), expr, + SQL('THEN'), _InFunction(value))) if self.default is not None: - clauses.extend((SQL('ELSE'), self.default)) + clauses.extend((SQL('ELSE'), _InFunction(self.default))) clauses.append(SQL('END')) with ctx(in_function=False): return ctx.sql(NodeList(clauses)) @@ -6956,9 +6967,10 @@ def dirty_fields(self): return [f for f in self._meta.sorted_fields if f.name in self._dirty] - def dependencies(self, search_nullable=False): + def dependencies(self, search_nullable=True): model_class = type(self) stack = [(type(self), None)] + queries = {} seen = set() while stack: @@ -6974,13 +6986,16 @@ subquery = (rel_model.select(rel_model._meta.primary_key) .where(node)) if not fk.null or search_nullable: + queries.setdefault(rel_model, []).append((node, fk)) stack.append((rel_model, subquery)) - yield (node, fk) + + for m in reversed(sort_models(seen)): + for sq, q in queries.get(m, ()): + yield sq, q def delete_instance(self, recursive=False, delete_nullable=False): if recursive: - dependencies = self.dependencies(delete_nullable) - for query, fk in reversed(list(dependencies)): + for query, fk in self.dependencies(): model = fk.model if fk.null and not delete_nullable: model.update(**{fk.name: None}).where(query).execute() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/playhouse/pool.py new/peewee-3.17.6/playhouse/pool.py --- old/peewee-3.17.5/playhouse/pool.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/playhouse/pool.py 2024-07-06 19:11:45.000000000 +0200 @@ -34,7 +34,6 @@ import functools import heapq import logging -import random import threading import time from collections import namedtuple @@ -48,6 +47,10 @@ TRANSACTION_STATUS_IDLE = \ TRANSACTION_STATUS_INERROR = \ TRANSACTION_STATUS_UNKNOWN = None +try: + from psycopg.pq import TransactionStatus +except ImportError: + pass from peewee import MySQLDatabase from peewee import PostgresqlDatabase @@ -68,6 +71,10 @@ PoolConnection = namedtuple('PoolConnection', ('timestamp', 'connection', 'checked_out')) +class _sentinel(object): + def __lt__(self, other): + return True + def locked(fn): @functools.wraps(fn) @@ -136,7 +143,8 @@ while True: try: # Remove the oldest connection from the heap. - ts, conn = heapq.heappop(self._connections) + ts, _, c_conn = heapq.heappop(self._connections) + conn = c_conn key = self.conn_key(conn) except IndexError: ts = conn = None @@ -198,7 +206,8 @@ super(PooledDatabase, self)._close(conn) elif self._can_reuse(conn): logger.debug('Returning %s to pool.', key) - heapq.heappush(self._connections, (pool_conn.timestamp, conn)) + heapq.heappush(self._connections, + (pool_conn.timestamp, _sentinel(), conn)) else: logger.debug('Closed %s.', key) @@ -224,7 +233,7 @@ @locked def close_idle(self): # Close any open connections that are not currently in-use. - for _, conn in self._connections: + for _, _, conn in self._connections: self._close(conn, close_conn=True) self._connections = [] @@ -249,7 +258,7 @@ # Close all connections -- available and in-use. Warning: may break any # active connections used by other threads. self.close() - for _, conn in self._connections: + for _, _, conn in self._connections: self._close(conn, close_conn=True) for pool_conn in self._in_use.values(): self._close(pool_conn.connection, close_conn=True) @@ -317,9 +326,9 @@ return True txn_status = conn.pgconn.transaction_status - if txn_status == conn.TransactionStatus.UNKNOWN: + if txn_status == TransactionStatus.UNKNOWN: return True - elif txn_status != conn.TransactionStatus.IDLE: + elif txn_status != TransactionStatus.IDLE: conn.rollback() return False @@ -328,11 +337,11 @@ # Do not return connection in an error state, as subsequent queries # will all fail. If the status is unknown then we lost the connection # to the server and the connection should not be re-used. - if txn_status == conn.TransactionStatus.UNKNOWN: + if txn_status == TransactionStatus.UNKNOWN: return False - elif txn_status == conn.TransactionStatus.INERROR: + elif txn_status == TransactionStatus.INERROR: conn.reset() - elif txn_status != conn.TransactionStatus.IDLE: + elif txn_status != TransactionStatus.IDLE: conn.rollback() return True except ImportError: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/playhouse/postgres_ext.py new/peewee-3.17.6/playhouse/postgres_ext.py --- old/peewee-3.17.5/playhouse/postgres_ext.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/playhouse/postgres_ext.py 2024-07-06 19:11:45.000000000 +0200 @@ -13,6 +13,7 @@ from peewee import Node from peewee import NodeList from peewee import __deprecated__ +from peewee import __exception_wrapper__ try: from psycopg2cffi import compat @@ -394,6 +395,13 @@ self.exhausted = False self.iterable = self.row_gen() + def __del__(self): + if self.cursor and not self.cursor.closed: + try: + self.cursor.close() + except Exception: + pass + @property def description(self): return self.cursor.description @@ -402,12 +410,15 @@ self.cursor.close() def row_gen(self): - while True: - rows = self.cursor.fetchmany(self.array_size) - if not rows: - return - for row in rows: - yield row + try: + while True: + rows = self.cursor.fetchmany(self.array_size) + if not rows: + return + for row in rows: + yield row + finally: + self.close() def fetchone(self): if self.exhausted: @@ -443,10 +454,9 @@ def ServerSide(query, database=None, array_size=None): if database is None: database = query._database - with database.transaction(): - server_side_query = ServerSideQuery(query, array_size=array_size) - for row in server_side_query: - yield row + server_side_query = ServerSideQuery(query, array_size=array_size) + for row in server_side_query: + yield row class _empty_object(object): @@ -477,7 +487,8 @@ else: raise InterfaceError('Error, database connection not opened.') if named_cursor: - curs = self._state.conn.cursor(name=str(uuid.uuid1())) + curs = self._state.conn.cursor(name=str(uuid.uuid1()), + withhold=True) return curs return self._state.conn.cursor() @@ -489,7 +500,16 @@ sql, params = ctx.sql(query).query() named_cursor = named_cursor or (self._server_side_cursors and sql[:6].lower() == 'select') - cursor = self.execute_sql(sql, params) + cursor = self.execute_sql(sql, params, named_cursor=named_cursor) if named_cursor: cursor = FetchManyCursor(cursor, array_size) return cursor + + def execute_sql(self, sql, params=None, commit=None, named_cursor=None): + if commit is not None: + __deprecated__('"commit" has been deprecated and is a no-op.') + logger.debug((sql, params)) + with __exception_wrapper__: + cursor = self.cursor(named_cursor=named_cursor) + cursor.execute(sql, params or ()) + return cursor diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/playhouse/psycopg3_ext.py new/peewee-3.17.6/playhouse/psycopg3_ext.py --- old/peewee-3.17.5/playhouse/psycopg3_ext.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/playhouse/psycopg3_ext.py 2024-07-06 19:11:45.000000000 +0200 @@ -16,6 +16,7 @@ try: import psycopg from psycopg.types.json import Jsonb + from psycopg.pq import TransactionStatus except ImportError: psycopg = Jsonb = None @@ -160,7 +161,7 @@ # connection. If the connection is in an error state or the connection # is otherwise unusable, return False. conn = self._state.conn - return conn.pgconn.transaction_status < conn.TransactionStatus.INERROR + return conn.pgconn.transaction_status < TransactionStatus.INERROR def extract_date(self, date_part, date_field): return fn.EXTRACT(NodeList((SQL(date_part), SQL('FROM'), date_field))) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/playhouse/reflection.py new/peewee-3.17.6/playhouse/reflection.py --- old/peewee-3.17.5/playhouse/reflection.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/playhouse/reflection.py 2024-07-06 19:11:45.000000000 +0200 @@ -92,7 +92,7 @@ if self.primary_key and not issubclass(self.field_class, AutoField): params['primary_key'] = True if self.default is not None: - params['constraints'] = '[SQL("DEFAULT %s")]' % self.default + params['constraints'] = '[SQL(\'DEFAULT %s\')]' % self.default # Handle ForeignKeyField-specific attributes. if self.is_foreign_key(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/tests/base.py new/peewee-3.17.6/tests/base.py --- old/peewee-3.17.5/tests/base.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/tests/base.py 2024-07-06 19:11:45.000000000 +0200 @@ -179,6 +179,12 @@ if params is not None: self.assertEqual(qparams, params) + def assertHistory(self, n, expected): + queries = [logrecord.msg for logrecord in self._qh.queries[-n:]] + queries = [(sql.replace('%s', '?').replace('`', '"'), params) + for sql, params in queries] + self.assertEqual(queries, expected) + @property def history(self): return self._qh.queries diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/tests/db_tests.py new/peewee-3.17.6/tests/db_tests.py --- old/peewee-3.17.5/tests/db_tests.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/tests/db_tests.py 2024-07-06 19:11:45.000000000 +0200 @@ -700,6 +700,35 @@ sorted_models = sort_models(list_of_models) self.assertEqual(sorted_models, models) + def test_sort_models_multi_fk(self): + class Inventory(Model): + pass + class Sheet(Model): + inventory = ForeignKeyField(Inventory) + class Program(Model): + inventory = ForeignKeyField(Inventory) + class ProgramSheet(Model): + program = ForeignKeyField(Program) + sheet = ForeignKeyField(Sheet) + class ProgramPart(Model): + program_sheet = ForeignKeyField(ProgramSheet) + class Offal(Model): + program_sheet = ForeignKeyField(ProgramSheet) + sheet = ForeignKeyField(Sheet) + + M = [Inventory, Sheet, Program, ProgramSheet, ProgramPart, Offal] + sorted_models = sort_models(M) + self.assertEqual(sorted_models, [ + Inventory, + Program, + Sheet, + ProgramSheet, + Offal, + ProgramPart, + ]) + for list_of_models in permutations(M): + self.assertEqual(sort_models(list_of_models), sorted_models) + class TestDBProxy(BaseTestCase): def test_proxy_context_manager(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/tests/models.py new/peewee-3.17.6/tests/models.py --- old/peewee-3.17.5/tests/models.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/tests/models.py 2024-07-06 19:11:45.000000000 +0200 @@ -1595,22 +1595,24 @@ def test_delete_instance_recursive(self): huey = User.get(User.username == 'huey') + a = [] + for d in huey.dependencies(): + a.append(d) with self.assertQueryCount(5): huey.delete_instance(recursive=True) - queries = [logrecord.msg for logrecord in self._qh.queries[-5:]] - self.assertEqual(sorted(queries), [ + self.assertHistory(5, [ + ('DELETE FROM "favorite" WHERE ("favorite"."user_id" = ?)', + [huey.id]), ('DELETE FROM "favorite" WHERE (' '"favorite"."tweet_id" IN (' 'SELECT "t1"."id" FROM "tweet" AS "t1" WHERE (' '"t1"."user_id" = ?)))', [huey.id]), - ('DELETE FROM "favorite" WHERE ("favorite"."user_id" = ?)', - [huey.id]), ('DELETE FROM "tweet" WHERE ("tweet"."user_id" = ?)', [huey.id]), - ('DELETE FROM "users" WHERE ("users"."id" = ?)', [huey.id]), ('UPDATE "account" SET "user_id" = ? ' 'WHERE ("account"."user_id" = ?)', [None, huey.id]), + ('DELETE FROM "users" WHERE ("users"."id" = ?)', [huey.id]), ]) # Only one user left. @@ -1638,17 +1640,16 @@ huey.delete_instance(recursive=True, delete_nullable=True) # Get the last 5 delete queries. - queries = [logrecord.msg for logrecord in self._qh.queries[-5:]] - self.assertEqual(sorted(queries), [ - ('DELETE FROM "account" WHERE ("account"."user_id" = ?)', + self.assertHistory(5, [ + ('DELETE FROM "favorite" WHERE ("favorite"."user_id" = ?)', [huey.id]), ('DELETE FROM "favorite" WHERE (' '"favorite"."tweet_id" IN (' 'SELECT "t1"."id" FROM "tweet" AS "t1" WHERE (' '"t1"."user_id" = ?)))', [huey.id]), - ('DELETE FROM "favorite" WHERE ("favorite"."user_id" = ?)', - [huey.id]), ('DELETE FROM "tweet" WHERE ("tweet"."user_id" = ?)', [huey.id]), + ('DELETE FROM "account" WHERE ("account"."user_id" = ?)', + [huey.id]), ('DELETE FROM "users" WHERE ("users"."id" = ?)', [huey.id]), ]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/tests/pool.py new/peewee-3.17.6/tests/pool.py --- old/peewee-3.17.5/tests/pool.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/tests/pool.py 2024-07-06 19:11:45.000000000 +0200 @@ -112,7 +112,7 @@ self.assertEqual(db.counter, 5) self.assertEqual( - sorted([conn for _, conn in db._connections]), + sorted([conn for _, _, conn in db._connections]), [1, 2, 3, 4, 5]) # All 5 are ready to be re-used. self.assertEqual(db._in_use, {}) @@ -184,9 +184,9 @@ db = FakePooledDatabase('testing', counter=3) now = time.time() - heapq.heappush(db._connections, (now - 10, 3)) - heapq.heappush(db._connections, (now - 5, 2)) - heapq.heappush(db._connections, (now - 1, 1)) + heapq.heappush(db._connections, (now - 10, None, 3)) + heapq.heappush(db._connections, (now - 5, None, 2)) + heapq.heappush(db._connections, (now - 1, None, 1)) self.assertEqual(db.connection(), 3) self.assertTrue(3 in db._in_use) @@ -218,9 +218,9 @@ db = FakePooledDatabase('testing', counter=3) now = time.time() - heapq.heappush(db._connections, (now - 10, 3)) - heapq.heappush(db._connections, (now - 5, 2)) - heapq.heappush(db._connections, (now - 1, 1)) + heapq.heappush(db._connections, (now - 10, None, 3)) + heapq.heappush(db._connections, (now - 5, None, 2)) + heapq.heappush(db._connections, (now - 1, None, 1)) self.assertEqual(db.connection(), 3) self.assertTrue(3 in db._in_use) @@ -234,10 +234,10 @@ now = time.time() db = FakePooledDatabase('testing', stale_timeout=10) conns = [ - (now - 20, 1), - (now - 15, 2), - (now - 5, 3), - (now, 4), + (now - 20, None, 1), + (now - 15, None, 2), + (now - 5, None, 3), + (now, None, 4), ] for ts_conn in conns: heapq.heappush(db._connections, ts_conn) @@ -245,7 +245,7 @@ self.assertEqual(db.connection(), 3) self.assertEqual(len(db._in_use), 1) self.assertTrue(3 in db._in_use) - self.assertEqual(db._connections, [(now, 4)]) + self.assertEqual(db._connections, [(now, None, 4)]) def test_connect_cascade(self): now = time.time() @@ -256,10 +256,10 @@ db = ClosedPooledDatabase('testing', stale_timeout=10) conns = [ - (now - 15, 1), # Skipped due to being stale. - (now - 5, 2), # Will appear closed. - (now - 3, 3), - (now, 4), # Will appear closed. + (now - 15, None, 1), # Skipped due to being stale. + (now - 5, None, 2), # Will appear closed. + (now - 3, None, 3), + (now, None, 4), # Will appear closed. ] db.counter = 4 # The next connection we create will have id=5. for ts_conn in conns: @@ -272,7 +272,7 @@ pool_conn = db._in_use[3] self.assertEqual(pool_conn.timestamp, now - 3) self.assertEqual(pool_conn.connection, 3) - self.assertEqual(db._connections, [(now, 4)]) + self.assertEqual(db._connections, [(now, None, 4)]) # Since conn 4 is closed, we will open a new conn. db._state.closed = True # Pretend we're in a different thread. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/tests/postgres.py new/peewee-3.17.6/tests/postgres.py --- old/peewee-3.17.5/tests/postgres.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/tests/postgres.py 2024-07-06 19:11:45.000000000 +0200 @@ -815,6 +815,16 @@ ss_query = ServerSide(query.where(SQL('1 = 0'))) self.assertEqual(list(ss_query), []) + def test_lower_level_apis(self): + query = Register.select(Register.value).order_by(Register.value) + ssq = ServerSideQuery(query, array_size=10) + curs_wrapper = ssq._execute(self.database) + curs = curs_wrapper.cursor + self.assertTrue(isinstance(curs, FetchManyCursor)) + self.assertEqual(curs.fetchone(), (0,)) + self.assertEqual(curs.fetchone(), (1,)) + curs.close() + class KX(TestModel): key = CharField(unique=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/tests/regressions.py new/peewee-3.17.6/tests/regressions.py --- old/peewee-3.17.5/tests/regressions.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/tests/regressions.py 2024-07-06 19:11:45.000000000 +0200 @@ -138,8 +138,7 @@ with self.assertQueryCount(5): a2.delete_instance(recursive=True) - queries = [logrecord.msg for logrecord in self._qh.queries[-5:]] - self.assertEqual(sorted(queries, reverse=True), [ + self.assertHistory(5, [ ('DELETE FROM "di_d" WHERE ("di_d"."c_id" IN (' 'SELECT "t1"."id" FROM "di_c" AS "t1" WHERE ("t1"."b_id" IN (' 'SELECT "t2"."id" FROM "di_b" AS "t2" WHERE ("t2"."a_id" = ?)' @@ -1823,3 +1822,69 @@ case = Case(None, [(Sample.id.in_(subq), Sample.value)], 0) q = Sample.select(fn.SUM(case)) self.assertEqual(q.scalar(), 4.0) + + +class I(TestModel): + name = TextField() +class S(TestModel): + i = ForeignKeyField(I) +class P(TestModel): + i = ForeignKeyField(I) +class PS(TestModel): + p = ForeignKeyField(P) + s = ForeignKeyField(S) +class PP(TestModel): + ps = ForeignKeyField(PS) +class O(TestModel): + ps = ForeignKeyField(PS) + s = ForeignKeyField(S) +class OX(TestModel): + o = ForeignKeyField(O, null=True) + +class TestDeleteInstanceDFS(ModelTestCase): + requires = [I, S, P, PS, PP, O, OX] + + def test_delete_instance_dfs(self): + i1, i2 = [I.create(name=n) for n in ('i1', 'i2')] + for i in (i1, i2): + s = S.create(i=i) + p = P.create(i=i) + ps = PS.create(p=p, s=s) + pp = PP.create(ps=ps) + o = O.create(ps=ps, s=s) + ox = OX.create(o=o) + + with self.assertQueryCount(9): + i1.delete_instance(recursive=True) + + self.assertHistory(9, [ + ('DELETE FROM "pp" WHERE (' + '"pp"."ps_id" IN (SELECT "t1"."id" FROM "ps" AS "t1" WHERE (' + '"t1"."p_id" IN (SELECT "t2"."id" FROM "p" AS "t2" WHERE (' + '"t2"."i_id" = ?)))))', [i1.id]), + ('UPDATE "ox" SET "o_id" = ? WHERE (' + '"ox"."o_id" IN (SELECT "t1"."id" FROM "o" AS "t1" WHERE (' + '"t1"."ps_id" IN (SELECT "t2"."id" FROM "ps" AS "t2" WHERE (' + '"t2"."p_id" IN (SELECT "t3"."id" FROM "p" AS "t3" WHERE (' + '"t3"."i_id" = ?)))))))', [None, i1.id]), + ('DELETE FROM "o" WHERE (' + '"o"."ps_id" IN (SELECT "t1"."id" FROM "ps" AS "t1" WHERE (' + '"t1"."p_id" IN (SELECT "t2"."id" FROM "p" AS "t2" WHERE (' + '"t2"."i_id" = ?)))))', [i1.id]), + ('DELETE FROM "o" WHERE (' + '"o"."s_id" IN (SELECT "t1"."id" FROM "s" AS "t1" WHERE (' + '"t1"."i_id" = ?)))', [i1.id]), + ('DELETE FROM "ps" WHERE (' + '"ps"."p_id" IN (SELECT "t1"."id" FROM "p" AS "t1" WHERE (' + '"t1"."i_id" = ?)))', [i1.id]), + ('DELETE FROM "ps" WHERE (' + '"ps"."s_id" IN (SELECT "t1"."id" FROM "s" AS "t1" WHERE (' + '"t1"."i_id" = ?)))', [i1.id]), + ('DELETE FROM "s" WHERE ("s"."i_id" = ?)', [i1.id]), + ('DELETE FROM "p" WHERE ("p"."i_id" = ?)', [i1.id]), + ('DELETE FROM "i" WHERE ("i"."id" = ?)', [i1.id]), + ]) + + counts = {OX: 2} + for m in self.requires: + self.assertEqual(m.select().count(), counts.get(m, 1)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/tests/sql.py new/peewee-3.17.6/tests/sql.py --- old/peewee-3.17.5/tests/sql.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/tests/sql.py 2024-07-06 19:11:45.000000000 +0200 @@ -1802,6 +1802,20 @@ 'CASE WHEN ("t1"."id" IN (SELECT "t1"."id" FROM "n" AS "t1")) ' 'THEN ? ELSE ? END) FROM "n" AS "t1"'), [1, 0]) + case = Case(None, [ + (Name.id < 5, Name.select(fn.SUM(Name.id))), + (Name.id > 5, Name.select(fn.COUNT(Name.name)).distinct())], + Name.select(fn.MAX(Name.id))) + q = Name.select(Name.name, case.alias('magic')) + self.assertSQL(q, ( + 'SELECT "t1"."name", CASE ' + 'WHEN ("t1"."id" < ?) ' + 'THEN (SELECT SUM("t1"."id") FROM "n" AS "t1") ' + 'WHEN ("t1"."id" > ?) ' + 'THEN (SELECT DISTINCT COUNT("t1"."name") FROM "n" AS "t1") ' + 'ELSE (SELECT MAX("t1"."id") FROM "n" AS "t1") END AS "magic" ' + 'FROM "n" AS "t1"'), [5, 5]) + class TestSelectFeatures(BaseTestCase): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.17.5/tests/sqlcipher_ext.py new/peewee-3.17.6/tests/sqlcipher_ext.py --- old/peewee-3.17.5/tests/sqlcipher_ext.py 2024-05-10 15:42:02.000000000 +0200 +++ new/peewee-3.17.6/tests/sqlcipher_ext.py 2024-07-06 19:11:45.000000000 +0200 @@ -11,7 +11,10 @@ PASSPHRASE = 'testing sqlcipher' -PRAGMAS = {'kdf_iter': 10} # Much faster for testing. Totally unsafe. +PRAGMAS = { + 'kdf_iter': 10, # Much faster for testing. Totally unsafe. + 'cipher_log_level': 'none', +} db = SqlCipherDatabase('peewee_test.dbc', passphrase=PASSPHRASE, pragmas=PRAGMAS) ext_db = SqlCipherExtDatabase('peewee_test.dbx', passphrase=PASSPHRASE,