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)