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 2023-11-07 21:28:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-peewee (Old)
 and      /work/SRC/openSUSE:Factory/.python-peewee.new.17445 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-peewee"

Tue Nov  7 21:28:37 2023 rev:24 rq:1124001 version:3.17.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-peewee/python-peewee.changes      
2023-08-22 08:56:21.990583896 +0200
+++ /work/SRC/openSUSE:Factory/.python-peewee.new.17445/python-peewee.changes   
2023-11-07 21:29:29.354430813 +0100
@@ -1,0 +2,20 @@
+Tue Nov  7 14:21:31 UTC 2023 - Dirk Müller <dmuel...@suse.com>
+
+- update to 3.17.0:
+  * Only roll-back in the outermost `@db.transaction`
+    decorator/ctx manager if an unhandled exception occurs.
+  * Cover transaction `BEGIN` in the reconnect-mixin. Given that
+    no transaction has been started, reconnecting when beginning
+    a new transaction ensures that a reconnect will occur if it
+    is safe to do so.
+  * Add support for setting `isolation_level` in `db.atomic()`
+    and `db.transaction()` when using Postgres and MySQL/MariaDB,
+    which will apply to the wrapped transaction.
+  * Add support for the Sqlite `SQLITE_DETERMINISTIC` function
+    flag. This allows user-defined Sqlite functions to be used
+    in indexes and may be used by the query planner.
+  * Fix unreported bug in dataset import when inferred field name
+    differs from column name.
+- disable apsw from tests for sle15 - can't be build anymore
+
+-------------------------------------------------------------------

Old:
----
  peewee-3.16.3.tar.gz

New:
----
  peewee-3.17.0.tar.gz

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

Other differences:
------------------
++++++ python-peewee.spec ++++++
--- /var/tmp/diff_new_pack.ALC4NL/_old  2023-11-07 21:29:29.774446279 +0100
+++ /var/tmp/diff_new_pack.ALC4NL/_new  2023-11-07 21:29:29.778446427 +0100
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-peewee
-Version:        3.16.3
+Version:        3.17.0
 Release:        0
 Summary:        An expressive ORM that supports multiple SQL backends
 License:        BSD-3-Clause
@@ -27,7 +27,9 @@
 BuildRequires:  %{python_module Cython}
 BuildRequires:  %{python_module Flask}
 BuildRequires:  %{python_module PyMySQL}
+%if 0%{?suse_version} > 1500
 BuildRequires:  %{python_module apsw}
+%endif
 BuildRequires:  %{python_module devel}
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module psycopg2}

++++++ peewee-3.16.3.tar.gz -> peewee-3.17.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/.readthedocs.yaml 
new/peewee-3.17.0/.readthedocs.yaml
--- old/peewee-3.16.3/.readthedocs.yaml 2023-08-14 16:15:01.000000000 +0200
+++ new/peewee-3.17.0/.readthedocs.yaml 2023-10-13 17:45:33.000000000 +0200
@@ -2,3 +2,11 @@
 python:
   install:
   - requirements: docs/requirements.txt
+
+build:
+  os: ubuntu-22.04
+  tools:
+    python: "3.11"
+
+sphinx:
+  configuration: docs/conf.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/CHANGELOG.md 
new/peewee-3.17.0/CHANGELOG.md
--- old/peewee-3.16.3/CHANGELOG.md      2023-08-14 16:15:01.000000000 +0200
+++ new/peewee-3.17.0/CHANGELOG.md      2023-10-13 17:45:33.000000000 +0200
@@ -7,7 +7,43 @@
 
 ## master
 
-[View commits](https://github.com/coleifer/peewee/compare/3.16.2...master)
+[View commits](https://github.com/coleifer/peewee/compare/3.17.0...master)
+
+## 3.17.0
+
+* Only roll-back in the outermost `@db.transaction` decorator/ctx manager if
+  an unhandled exception occurs. Previously, an unhandled exception that
+  occurred in a nested `transaction` context would trigger a rollback. The use
+  of nested `transaction` has long been discouraged in the documentation: the
+  recommendation is to always use `db.atomic`, which will use savepoints to
+  properly handle nested blocks. However, the new behavior should make it
+  easier to reason about transaction boundaries - see #2767 for discussion.
+* Cover transaction `BEGIN` in the reconnect-mixin. Given that no transaction
+  has been started, reconnecting when beginning a new transaction ensures that
+  a reconnect will occur if it is safe to do so.
+* Add support for setting `isolation_level` in `db.atomic()` and
+  `db.transaction()` when using Postgres and MySQL/MariaDB, which will apply to
+  the wrapped transaction. Note: Sqlite has supported a similar `lock_type`
+  parameter for some time.
+* Add support for the Sqlite `SQLITE_DETERMINISTIC` function flag. This allows
+  user-defined Sqlite functions to be used in indexes and may be used by the
+  query planner.
+* Fix unreported bug in dataset import when inferred field name differs from
+  column name.
+
+[View commits](https://github.com/coleifer/peewee/compare/3.16.3...3.17.0)
+
+## 3.16.3
+
+* Support for Cython 3.0.
+* Add flag to `ManyToManyField` to prevent setting/getting values on unsaved
+  instances. This is worthwhile, since reading or writing a many-to-many has no
+  meaning when the instance is unsaved.
+* Adds a `star()` helper to `Source` base-class for selecting all columns.
+* Fix missing `binary` types for mysql-connector and mariadb-connector.
+* Add `extract()` method to MySQL `JSONField` for extracting a jsonpath.
+
+[View commits](https://github.com/coleifer/peewee/compare/3.16.2...3.16.3)
 
 ## 3.16.2
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/docs/conf.py 
new/peewee-3.17.0/docs/conf.py
--- old/peewee-3.16.3/docs/conf.py      2023-08-14 16:15:01.000000000 +0200
+++ new/peewee-3.17.0/docs/conf.py      2023-10-13 17:45:33.000000000 +0200
@@ -27,7 +27,7 @@
 
 # Add any Sphinx extension module names here, as strings. They can be 
extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['sphinx.ext.autodoc']
+extensions = ['sphinx.ext.autodoc', 'sphinx_rtd_theme']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
@@ -96,7 +96,7 @@
 
 # The theme to use for HTML and HTML Help pages.  See the documentation for
 # a list of builtin themes.
-html_theme = 'default'
+html_theme = 'sphinx_rtd_theme'
 
 # Theme options are theme-specific and customize the look and feel of a theme
 # further.  For a list of options available for each theme, see the
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/docs/peewee/api.rst 
new/peewee-3.17.0/docs/peewee/api.rst
--- old/peewee-3.16.3/docs/peewee/api.rst       2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/docs/peewee/api.rst       2023-10-13 17:45:33.000000000 
+0200
@@ -177,7 +177,7 @@
         :returns: whether or not a transaction is currently open.
         :rtype: bool
 
-    .. py:method:: atomic()
+    .. py:method:: atomic([...])
 
         Create a context-manager which runs any queries in the wrapped block in
         a transaction (or save-point if blocks are nested).
@@ -186,6 +186,15 @@
 
         :py:meth:`~Database.atomic` can also be used as a decorator.
 
+        Database-specific parameters:
+
+        :py:class:`PostgresqlDatabase` and :py:class:`MySQLDatabase` accept an
+        ``isolation_level`` parameter. :py:class:`SqliteDatabase` accepts a
+        ``lock_type`` parameter.
+
+        :param str isolation_level: Isolation strategy: SERIALIZABLE, READ 
COMMITTED, REPEATABLE READ, READ UNCOMMITTED
+        :param str lock_type: Locking strategy: DEFERRED, IMMEDIATE, EXCLUSIVE.
+
         Example code::
 
             with db.atomic() as txn:
@@ -273,11 +282,20 @@
         Roll back any changes made during a transaction begun with
         :py:meth:`~Database.session_start`.
 
-    .. py:method:: transaction()
+    .. py:method:: transaction([...])
 
         Create a context-manager that runs all queries in the wrapped block in
         a transaction.
 
+        Database-specific parameters:
+
+        :py:class:`PostgresqlDatabase` and :py:class:`MySQLDatabase` accept an
+        ``isolation_level`` parameter. :py:class:`SqliteDatabase` accepts a
+        ``lock_type`` parameter.
+
+        :param str isolation_level: Isolation strategy: SERIALIZABLE, READ 
COMMITTED, REPEATABLE READ, READ UNCOMMITTED
+        :param str lock_type: Locking strategy: DEFERRED, IMMEDIATE, EXCLUSIVE.
+
         .. warning::
             Calls to ``transaction`` cannot be nested. Only the top-most call
             will take effect. Rolling-back or committing a nested transaction
@@ -732,23 +750,31 @@
         extra attribute provides a shorthand way to generate the SQL necessary
         to use our custom collation.
 
-    .. py:method:: register_function(fn[, name=None[, num_params=-1]])
+    .. py:method:: register_function(fn[, name=None[, num_params=-1[, 
deterministic=None]]])
 
         :param fn: The user-defined scalar function.
         :param str name: Name of function (defaults to function name)
         :param int num_params: Number of arguments the function accepts, or
             -1 for any number.
+        :param bool deterministic: Whether the function is deterministic for a
+            given input (this is required to use the function in an index).
+            Requires Sqlite 3.20 or newer, and ``sqlite3`` driver support
+            (added to stdlib in Python 3.8).
 
         Register a user-defined scalar function. The function will be
         registered each time a new connection is opened.  Additionally, if a
         connection is already open, the function will be registered with the
         open connection.
 
-    .. py:method:: func([name=None[, num_params=-1]])
+    .. py:method:: func([name=None[, num_params=-1[, deterministic=None]]])
 
         :param str name: Name of the function (defaults to function name).
         :param int num_params: Number of parameters the function accepts,
             or -1 for any number.
+        :param bool deterministic: Whether the function is deterministic for a
+            given input (this is required to use the function in an index).
+            Requires Sqlite 3.20 or newer, and ``sqlite3`` driver support
+            (added to stdlib in Python 3.8).
 
         Decorator to register a user-defined scalar function.
 
@@ -928,6 +954,15 @@
         currently connected, the attached database will be detached from the
         open connection.
 
+    .. py:method:: atomic([lock_type=None])
+
+        :param str lock_type: Locking strategy: DEFERRED, IMMEDIATE, EXCLUSIVE.
+
+        Create an atomic context-manager, optionally using the specified
+        locking strategy (if unspecified, DEFERRED is used).
+
+        .. note:: Lock type only applies to the outermost ``atomic()`` block.
+
     .. py:method:: transaction([lock_type=None])
 
         :param str lock_type: Locking strategy: DEFERRED, IMMEDIATE, EXCLUSIVE.
@@ -955,11 +990,44 @@
         Set the timezone on the current connection. If no connection is open,
         then one will be opened.
 
+    .. py:method:: atomic([isolation_level=None])
+
+        :param str isolation_level: Isolation strategy: SERIALIZABLE, READ 
COMMITTED, REPEATABLE READ, READ UNCOMMITTED
+
+        Create an atomic context-manager, optionally using the specified
+        isolation level (if unspecified, the server default will be used).
+
+        .. note:: Isolation level only applies to the outermost ``atomic()`` 
block.
+
+    .. py:method:: transaction([isolation_level=None])
+
+        :param str isolation_level: Isolation strategy: SERIALIZABLE, READ 
COMMITTED, REPEATABLE READ, READ UNCOMMITTED
+
+        Create a transaction context-manager, optionally using the specified
+        isolation level (if unspecified, the server default will be used).
+
 
 .. py:class:: MySQLDatabase(database[, **kwargs])
 
     MySQL database implementation.
 
+    .. py:method:: atomic([isolation_level=None])
+
+        :param str isolation_level: Isolation strategy: SERIALIZABLE, READ 
COMMITTED, REPEATABLE READ, READ UNCOMMITTED
+
+        Create an atomic context-manager, optionally using the specified
+        isolation level (if unspecified, the server default will be used).
+
+        .. note:: Isolation level only applies to the outermost ``atomic()`` 
block.
+
+    .. py:method:: transaction([isolation_level=None])
+
+        :param str isolation_level: Isolation strategy: SERIALIZABLE, READ 
COMMITTED, REPEATABLE READ, READ UNCOMMITTED
+
+        Create a transaction context-manager, optionally using the specified
+        isolation level (if unspecified, the server default will be used).
+
+
 .. _query-builder-api:
 
 Query-builder
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/docs/peewee/database.rst 
new/peewee-3.17.0/docs/peewee/database.rst
--- old/peewee-3.16.3/docs/peewee/database.rst  2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/docs/peewee/database.rst  2023-10-13 17:45:33.000000000 
+0200
@@ -1503,9 +1503,27 @@
 transactions. :py:meth:`~Database.atomic` blocks will be run in a transaction
 or savepoint, depending on the level of nesting.
 
-If an exception occurs in a wrapped block, the current transaction/savepoint
-will be rolled back. Otherwise the statements will be committed at the end of
-the wrapped block.
+If an unhandled exception occurs in a wrapped block, the current
+transaction/savepoint will be rolled back. Otherwise the statements will be
+committed at the end of the wrapped block.
+
+Examples:
+
+.. code-block:: python
+
+    # Transaction will commit automatically at the end of the "with" block:
+    with db.atomic() as txn:
+        User.create(username='u1')
+
+    # Unhandled exceptions will cause transaction to be rolled-back:
+    with db.atomic() as txn:
+        User.create(username='huey')
+        # User has been INSERTed into the database but the transaction is not
+        # yet committed because we haven't left the scope of the "with" block.
+
+        raise ValueError('uh-oh')
+        # This exception is unhandled - the transaction will be rolled-back and
+        # the ValueError will be raised.
 
 .. note::
     While inside a block wrapped by the :py:meth:`~Database.atomic` context
@@ -1656,8 +1674,11 @@
 .. note::
     If you attempt to nest transactions with peewee using the
     :py:meth:`~Database.transaction` context manager, only the outer-most
-    transaction will be used. However if an exception occurs in a nested block,
-    this can lead to unpredictable behavior, so it is strongly recommended that
+    transaction will be used. If an exception occurs in a nested block, the
+    transaction will NOT be rolled-back -- only exceptions that bubble-up to
+    the outer-most transaction will trigger a rollback.
+
+    As this may lead to unpredictable behavior, it is recommended that
     you use :py:meth:`~Database.atomic`.
 
 Explicit Savepoints
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/docs/peewee/query_operators.rst 
new/peewee-3.17.0/docs/peewee/query_operators.rst
--- old/peewee-3.16.3/docs/peewee/query_operators.rst   2023-08-14 
16:15:01.000000000 +0200
+++ new/peewee-3.17.0/docs/peewee/query_operators.rst   2023-10-13 
17:45:33.000000000 +0200
@@ -34,7 +34,7 @@
 ``.contains(substr)``   Wild-card search for substring.
 ``.startswith(prefix)`` Search for values beginning with ``prefix``.
 ``.endswith(suffix)``   Search for values ending with ``suffix``.
-``.between(low, high)`` Search for values between ``low`` and ``high``.
+``.between(low, high)`` Search where ``low <= value <= high``.
 ``.regexp(exp)``        Regular expression match (case-sensitive).
 ``.iregexp(exp)``       Regular expression match (case-insensitive).
 ``.bin_and(value)``     Binary AND.
@@ -65,6 +65,7 @@
     # Find the users whose username is in [charlie, huey, mickey]
     User.select().where(User.username.in_(['charlie', 'huey', 'mickey']))
 
+    # Find users whose salary is between 50k and 60k (inclusive).
     Employee.select().where(Employee.salary.between(50000, 60000))
 
     Employee.select().where(Employee.name.startswith('C'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/docs/peewee/quickstart.rst 
new/peewee-3.17.0/docs/peewee/quickstart.rst
--- old/peewee-3.16.3/docs/peewee/quickstart.rst        2023-08-14 
16:15:01.000000000 +0200
+++ new/peewee-3.17.0/docs/peewee/quickstart.rst        2023-10-13 
17:45:33.000000000 +0200
@@ -317,7 +317,8 @@
     # Bob 1960-01-15
     # Grandma L. 1935-03-01
 
-Now let's do the opposite. People whose birthday is between 1940 and 1960:
+Now let's do the opposite. People whose birthday is between 1940 and 1960
+(inclusive of both years):
 
 .. code-block:: python
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/docs/requirements.txt 
new/peewee-3.17.0/docs/requirements.txt
--- old/peewee-3.16.3/docs/requirements.txt     2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/docs/requirements.txt     2023-10-13 17:45:33.000000000 
+0200
@@ -1 +1,2 @@
 docutils<0.18
+sphinx-rtd-theme
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/peewee.py new/peewee-3.17.0/peewee.py
--- old/peewee-3.16.3/peewee.py 2023-08-14 16:15:01.000000000 +0200
+++ new/peewee-3.17.0/peewee.py 2023-10-13 17:45:33.000000000 +0200
@@ -70,7 +70,7 @@
         mysql = None
 
 
-__version__ = '3.16.3'
+__version__ = '3.17.0'
 __all__ = [
     'AnyField',
     'AsIs',
@@ -757,6 +757,7 @@
 
     def __getattr__(self, attr):
         return Column(self.node, attr)
+    __getitem__ = __getattr__
 
 
 class _DynamicColumn(object):
@@ -3623,8 +3624,9 @@
             conn.create_collation(name, fn)
 
     def _load_functions(self, conn):
-        for name, (fn, num_params) in self._functions.items():
-            conn.create_function(name, num_params, fn)
+        for name, (fn, n_params, deterministic) in self._functions.items():
+            kwargs = {'deterministic': deterministic} if deterministic else {}
+            conn.create_function(name, n_params, fn, **kwargs)
 
     def _load_window_functions(self, conn):
         for name, (klass, num_params) in self._window_functions.items():
@@ -3657,14 +3659,15 @@
             return fn
         return decorator
 
-    def register_function(self, fn, name=None, num_params=-1):
-        self._functions[name or fn.__name__] = (fn, num_params)
+    def register_function(self, fn, name=None, num_params=-1,
+                          deterministic=None):
+        self._functions[name or fn.__name__] = (fn, num_params, deterministic)
         if not self.is_closed():
             self._load_functions(self.connection())
 
-    def func(self, name=None, num_params=-1):
+    def func(self, name=None, num_params=-1, deterministic=None):
         def decorator(fn):
-            self.register_function(fn, name, num_params)
+            self.register_function(fn, name, num_params, deterministic)
             return fn
         return decorator
 
@@ -3966,6 +3969,16 @@
         except AttributeError:
             return cursor.cursor.rowcount
 
+    def begin(self, isolation_level=None):
+        if self.is_closed():
+            self.connect()
+        if isolation_level:
+            stmt = 'BEGIN TRANSACTION ISOLATION LEVEL %s' % isolation_level
+        else:
+            stmt = 'BEGIN'
+        with __exception_wrapper__:
+            self.cursor().execute(stmt)
+
     def get_tables(self, schema=None):
         query = ('SELECT tablename FROM pg_catalog.pg_tables '
                  'WHERE schemaname = %s ORDER BY tablename')
@@ -4184,6 +4197,16 @@
     def default_values_insert(self, ctx):
         return ctx.literal('() VALUES ()')
 
+    def begin(self, isolation_level=None):
+        if self.is_closed():
+            self.connect()
+        with __exception_wrapper__:
+            curs = self.cursor()
+            if isolation_level:
+                curs.execute('SET TRANSACTION ISOLATION LEVEL %s' %
+                             isolation_level)
+            curs.execute('BEGIN')
+
     def get_tables(self, schema=None):
         query = ('SELECT table_name FROM information_schema.tables '
                  'WHERE table_schema = DATABASE() AND table_type != %s '
@@ -4405,10 +4428,11 @@
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
+        depth = self.db.transaction_depth()
         try:
-            if exc_type:
+            if exc_type and depth == 1:
                 self.rollback(False)
-            elif self.db.transaction_depth() == 1:
+            elif depth == 1:
                 try:
                     self.commit(False)
                 except:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/playhouse/apsw_ext.py 
new/peewee-3.17.0/playhouse/apsw_ext.py
--- old/peewee-3.16.3/playhouse/apsw_ext.py     2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/playhouse/apsw_ext.py     2023-10-13 17:45:33.000000000 
+0200
@@ -76,8 +76,9 @@
             conn.createcollation(name, fn)
 
     def _load_functions(self, conn):
-        for name, (fn, num_params) in self._functions.items():
-            conn.createscalarfunction(name, fn, num_params)
+        for name, (fn, num_params, deterministic) in self._functions.items():
+            args = (deterministic,) if deterministic else ()
+            conn.createscalarfunction(name, fn, num_params, *args)
 
     def _load_extensions(self, conn):
         conn.enableloadextension(True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/playhouse/dataset.py 
new/peewee-3.17.0/playhouse/dataset.py
--- old/peewee-3.16.3/playhouse/dataset.py      2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/playhouse/dataset.py      2023-10-13 17:45:33.000000000 
+0200
@@ -242,6 +242,7 @@
 
     def _migrate_new_columns(self, data):
         new_keys = set(data) - set(self.model_class._meta.fields)
+        new_keys -= set(self.model_class._meta.columns)
         if new_keys:
             operations = []
             for key in new_keys:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/playhouse/migrate.py 
new/peewee-3.17.0/playhouse/migrate.py
--- old/peewee-3.16.3/playhouse/migrate.py      2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/playhouse/migrate.py      2023-10-13 17:45:33.000000000 
+0200
@@ -1,9 +1,6 @@
 """
 Lightweight schema migrations.
 
-NOTE: Currently tested with SQLite and Postgresql. MySQL may be missing some
-features.
-
 Example Usage
 -------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/playhouse/shortcuts.py 
new/peewee-3.17.0/playhouse/shortcuts.py
--- old/peewee-3.16.3/playhouse/shortcuts.py    2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/playhouse/shortcuts.py    2023-10-13 17:45:33.000000000 
+0200
@@ -252,8 +252,14 @@
     def execute_sql(self, sql, params=None, commit=None):
         if commit is not None:
             __deprecated__('"commit" has been deprecated and is a no-op.')
+        return self._reconnect(super(ReconnectMixin, self).execute_sql, sql, 
params)
+
+    def begin(self):
+        return self._reconnect(super(ReconnectMixin, self).begin)
+
+    def _reconnect(self, func, *args, **kwargs):
         try:
-            return super(ReconnectMixin, self).execute_sql(sql, params)
+            return func(*args, **kwargs)
         except Exception as exc:
             # If we are in a transaction, do not reconnect silently as
             # any changes could be lost.
@@ -275,7 +281,7 @@
                 self.close()
                 self.connect()
 
-            return super(ReconnectMixin, self).execute_sql(sql, params)
+            return func(*args, **kwargs)
 
 
 def resolve_multimodel_query(query, key='_model_identifier'):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/tests/dataset.py 
new/peewee-3.17.0/tests/dataset.py
--- old/peewee-3.16.3/tests/dataset.py  2023-08-14 16:15:01.000000000 +0200
+++ new/peewee-3.17.0/tests/dataset.py  2023-10-13 17:45:33.000000000 +0200
@@ -464,6 +464,18 @@
         self.assertEqual(table.columns, ['id', 'name'])
         self.assertEqual(list(table.all()), [{'id': 1, 'name': 'charlie'}])
 
+    def test_table_column_creation_field_col(self):
+        table = self.dataset['people']
+        table.insert(**{'First Name': 'charlie'})
+        self.assertEqual(table.columns, ['id', 'First_Name'])
+        self.assertEqual(list(table.all()), [{'id': 1, 'First_Name': 
'charlie'}])
+
+        table.insert(**{'First Name': 'huey'})
+        self.assertEqual(table.columns, ['id', 'First_Name'])
+        self.assertEqual(list(table.all().order_by(table.model_class.id)), [
+            {'id': 1, 'First_Name': 'charlie'},
+            {'id': 2, 'First_Name': 'huey'}])
+
     def test_import_json(self):
         table = self.dataset['people']
         table.insert(name='charlie')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/tests/db_tests.py 
new/peewee-3.17.0/tests/db_tests.py
--- old/peewee-3.16.3/tests/db_tests.py 2023-08-14 16:15:01.000000000 +0200
+++ new/peewee-3.17.0/tests/db_tests.py 2023-10-13 17:45:33.000000000 +0200
@@ -591,6 +591,7 @@
             return view_meta.name, (sql_ws_norm
                                     .replace('`peewee_test`.', '')
                                     .replace('`notes`.', '')
+                                    .replace('notes.', '')
                                     .replace('`', ''))
 
         def assertViews(expected):
@@ -637,11 +638,11 @@
         elif IS_POSTGRESQL:
             assertViews([
                 ('notes_deleted',
-                 ('SELECT notes.content FROM notes '
-                  'WHERE (notes.status = 9) ORDER BY notes.id DESC')),
+                 ('SELECT content FROM notes '
+                  'WHERE (status = 9) ORDER BY id DESC')),
                 ('notes_public',
-                 ('SELECT notes.content, notes.ts FROM notes '
-                  'WHERE (notes.status = 1) ORDER BY notes.ts DESC'))])
+                 ('SELECT content, ts FROM notes '
+                  'WHERE (status = 1) ORDER BY ts DESC'))])
         elif IS_CRDB:
             assertViews([
                 ('notes_deleted',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/tests/shortcuts.py 
new/peewee-3.17.0/tests/shortcuts.py
--- old/peewee-3.16.3/tests/shortcuts.py        2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/tests/shortcuts.py        2023-10-13 17:45:33.000000000 
+0200
@@ -582,7 +582,7 @@
 
         # The first (0th) query fails, as do all queries after the 2nd (1st).
         if self._query_counter != 1:
-            def _fake_execute(self, _):
+            def _fake_execute(self, *args):
                 raise OperationalError('2006')
             cursor.execute = _fake_execute
         self._query_counter += 1
@@ -601,7 +601,7 @@
 class TestReconnectMixin(DatabaseTestCase):
     database = db_loader('mysql', db_class=ReconnectMySQLDatabase)
 
-    def test_reconnect_mixin(self):
+    def test_reconnect_mixin_execute_sql(self):
         # Verify initial state.
         self.database._reset_mock()
         self.assertEqual(self.database._close_counter, 0)
@@ -625,6 +625,40 @@
         self.assertEqual(self.database._close_counter, 1)
 
 
+    def test_reconnect_mixin_begin(self):
+        # Verify initial state.
+        self.database._reset_mock()
+        self.assertEqual(self.database._close_counter, 0)
+
+        with self.database.atomic():
+            self.assertTrue(self.database.in_transaction())
+            self.assertEqual(self.database._close_counter, 1)
+            # Prepare mock for commit call
+            self.database._query_counter = 1
+
+        # Due to how we configured our mock, our queries are now failing and we
+        # can verify a reconnect is occuring *AND* the exception is propagated.
+        self.assertRaises(OperationalError, self.database.atomic().__enter__)
+        self.assertEqual(self.database._close_counter, 2)
+        self.assertFalse(self.database.in_transaction())
+
+        # We reset the mock counters. The first query we execute will fail. The
+        # second query will succeed (which happens automatically, thanks to the
+        # retry logic).
+        self.database._reset_mock()
+        with self.database.atomic():
+            self.assertTrue(self.database.in_transaction())
+            self.assertEqual(self.database._close_counter, 1)
+
+            # Do not reconnect when nesting transactions
+            self.assertRaises(OperationalError, 
self.database.atomic().__enter__)
+            self.assertEqual(self.database._close_counter, 1)
+
+            # Prepare mock for commit call
+            self.database._query_counter = 1
+        self.assertFalse(self.database.in_transaction())
+
+
 class MMA(TestModel):
     key = TextField()
     value = IntegerField()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/tests/sql.py 
new/peewee-3.17.0/tests/sql.py
--- old/peewee-3.16.3/tests/sql.py      2023-08-14 16:15:01.000000000 +0200
+++ new/peewee-3.17.0/tests/sql.py      2023-10-13 17:45:33.000000000 +0200
@@ -30,6 +30,14 @@
             'FROM "users" AS "t1" '
             'WHERE ("t1"."username" = ?)'), ['foo'])
 
+        query = (User
+                 .select(User.c['id'], User.c['username'])
+                 .where(User.c['username'] == 'test'))
+        self.assertSQL(query, (
+            'SELECT "t1"."id", "t1"."username" '
+            'FROM "users" AS "t1" '
+            'WHERE ("t1"."username" = ?)'), ['test'])
+
     def test_select_extend(self):
         query = User.select(User.c.id, User.c.username)
         self.assertSQL(query, (
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/tests/sqlite.py 
new/peewee-3.17.0/tests/sqlite.py
--- old/peewee-3.16.3/tests/sqlite.py   2023-08-14 16:15:01.000000000 +0200
+++ new/peewee-3.17.0/tests/sqlite.py   2023-10-13 17:45:33.000000000 +0200
@@ -2616,3 +2616,29 @@
         tweet = Tweet(user=user, content='t1')
         user.save()
         tweet.save()
+
+
+@skip_unless(database.server_version >= (3, 20, 0), 'sqlite deterministic 
requires >= 3.20')
+@skip_unless(sys.version_info >= (3, 8, 0), 'sqlite deterministic requires 
Python >= 3.8')
+class TestDeterministicFunction(ModelTestCase):
+    database = get_in_memory_db()
+
+    def test_deterministic(self):
+        db = self.database
+        @db.func(deterministic=True)
+        def pylower(s):
+            if s is not None:
+                return s.lower()
+
+        class Reg(db.Model):
+            key = TextField()
+            class Meta:
+                indexes = [
+                    SQL('create unique index "reg_pylower_key" '
+                        'on "reg" (pylower("key"))')]
+
+        db.create_tables([Reg])
+        Reg.create(key='k1')
+        with self.assertRaises(IntegrityError):
+            with db.atomic():
+                Reg.create(key='K1')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/peewee-3.16.3/tests/transactions.py 
new/peewee-3.17.0/tests/transactions.py
--- old/peewee-3.16.3/tests/transactions.py     2023-08-14 16:15:01.000000000 
+0200
+++ new/peewee-3.17.0/tests/transactions.py     2023-10-13 17:45:33.000000000 
+0200
@@ -3,6 +3,8 @@
 from .base import DatabaseTestCase
 from .base import IS_CRDB
 from .base import IS_CRDB_NESTED_TX
+from .base import IS_MYSQL
+from .base import IS_POSTGRESQL
 from .base import IS_SQLITE
 from .base import ModelTestCase
 from .base import db
@@ -139,6 +141,17 @@
 
         self.assertRegister([3, 4, 5])
 
+        with db.transaction() as txn:
+            self._save(6)
+            try:
+                with db.transaction() as txn2:
+                    self._save(7)
+                    raise ValueError()
+            except ValueError:
+                pass
+
+        self.assertRegister([3, 4, 5, 6, 7])
+
     @requires_nested
     def test_savepoint_commit(self):
         with db.atomic() as txn:
@@ -387,3 +400,72 @@
                 with db2.transaction(lock_type='IMMEDIATE') as t2:
                     self._save(6)
         self.assertRegister([2, 4, 5])
+
+
+class TestTransactionIsolationLevel(BaseTransactionTestCase):
+    @skip_unless(IS_POSTGRESQL, 'requires postgresql')
+    def test_isolation_level_pg(self):
+        db2 = new_connection()
+        db2.connect()
+
+        with db2.atomic(isolation_level='SERIALIZABLE'):
+            with db.atomic(isolation_level='SERIALIZABLE'):
+                self._save(1)
+                self.assertDB2(db2, [])
+            self.assertDB2(db2, [])
+        self.assertDB2(db2, [1])
+
+        with db2.atomic(isolation_level='READ COMMITTED'):
+            with db.atomic():
+                self._save(2)
+                self.assertDB2(db2, [1])
+            self.assertDB2(db2, [1, 2])
+        self.assertDB2(db2, [1, 2])
+
+        # NB: Read Uncommitted is treated as Read Committed by PG, so we don't
+        # test it here.
+
+        with db2.atomic(isolation_level='REPEATABLE READ'):
+            with db.atomic(isolation_level='REPEATABLE READ'):
+                self._save(3)
+                self.assertDB2(db2, [1, 2])
+            self.assertDB2(db2, [1, 2])
+        self.assertDB2(db2, [1, 2, 3])
+
+    @skip_unless(IS_MYSQL, 'requires mysql')
+    def test_isolation_level_mysql(self):
+        db2 = new_connection()
+        db2.connect()
+
+        with db2.atomic():
+            with db.atomic(isolation_level='SERIALIZABLE'):
+                self._save(1)
+                self.assertDB2(db2, [])
+            self.assertDB2(db2, [])
+        self.assertDB2(db2, [1])
+
+        with db2.atomic(isolation_level='READ COMMITTED'):
+            with db.atomic():
+                self._save(2)
+                self.assertDB2(db2, [1])
+            self.assertDB2(db2, [1, 2])
+        self.assertDB2(db2, [1, 2])
+
+        with db2.atomic(isolation_level='READ UNCOMMITTED'):
+            with db.atomic():
+                self._save(3)
+                self.assertDB2(db2, [1, 2, 3])
+            self.assertDB2(db2, [1, 2, 3])
+        self.assertDB2(db2, [1, 2, 3])
+
+        with db2.atomic(isolation_level='REPEATABLE READ'):
+            with db.atomic(isolation_level='REPEATABLE READ'):
+                self._save(4)
+                self.assertDB2(db2, [1, 2, 3])
+            self.assertDB2(db2, [1, 2, 3])
+        self.assertDB2(db2, [1, 2, 3, 4])
+
+    def assertDB2(self, db2, vals):
+        with Register.bind_ctx(db2):
+            q = Register.select().order_by(Register.value)
+            self.assertEqual([r.value for r in q], vals)

Reply via email to