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 2022-04-17 23:50:41 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-peewee (Old) and /work/SRC/openSUSE:Factory/.python-peewee.new.1941 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-peewee" Sun Apr 17 23:50:41 2022 rev:17 rq:970474 version:3.14.10 Changes: -------- --- /work/SRC/openSUSE:Factory/python-peewee/python-peewee.changes 2021-11-13 22:49:14.389282814 +0100 +++ /work/SRC/openSUSE:Factory/.python-peewee.new.1941/python-peewee.changes 2022-04-17 23:52:11.106499863 +0200 @@ -1,0 +2,25 @@ +Sat Apr 16 15:24:55 UTC 2022 - Matej Cepl <[email protected]> + +- Update to 3.14.10: + - Add shortcut for conditional insert using sub-select + - Add convenience left_outer_join() method to query. + - Add selected_columns property to Select queries. + - Add name property to Alias instances. + - Fix regression in tests introduced by change to DataSet in + 3.14.9. + - Allow calling table_exists() with a model-class, refs + - Improve is_connection_usable() method of MySQLDatabase class. + - Better support for VIEWs with playhouse.dataset.DataSet and + sqlite-web. + - Support INSERT / ON CONFLICT in playhosue.kv for newer + Sqlite. + - Add ArrayField.contained_by() method, a corollary to + contains() and the contains_any() methods. + - Support cyclical foreign-key relationships in + reflection/introspection, and also for sqlite-web. + - Add magic methods for FTS5 field to optimize, rebuild and + integrity check the full-text index. + - Add fallbacks in setup.py in the event distutils is not + available. + +------------------------------------------------------------------- Old: ---- peewee-3.14.8.tar.gz New: ---- peewee-3.14.10.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-peewee.spec ++++++ --- /var/tmp/diff_new_pack.g74Jyp/_old 2022-04-17 23:52:11.494500394 +0200 +++ /var/tmp/diff_new_pack.g74Jyp/_new 2022-04-17 23:52:11.498500399 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-peewee # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-peewee -Version: 3.14.8 +Version: 3.14.10 Release: 0 Summary: An expressive ORM that supports multiple SQL backends License: BSD-3-Clause ++++++ peewee-3.14.8.tar.gz -> peewee-3.14.10.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/.readthedocs.yaml new/peewee-3.14.10/.readthedocs.yaml --- old/peewee-3.14.8/.readthedocs.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/peewee-3.14.10/.readthedocs.yaml 2022-03-07 18:18:46.000000000 +0100 @@ -0,0 +1,4 @@ +version: 2 +python: + install: + - requirements: docs/requirements.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/CHANGELOG.md new/peewee-3.14.10/CHANGELOG.md --- old/peewee-3.14.8/CHANGELOG.md 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/CHANGELOG.md 2022-03-07 18:18:46.000000000 +0100 @@ -7,7 +7,38 @@ ## master -[View commits](https://github.com/coleifer/peewee/compare/3.14.8...master) +* Add `shortcuts.insert_where()` helper for generating conditional INSERT with + a bit less boilerplate. +* Fix bug in `test_utils.count_queres()` which could erroneously include pool + events such as connect/disconnect, etc. + +[View commits](https://github.com/coleifer/peewee/compare/3.14.10...master) + +## 3.14.10 + +* Add shortcut for conditional insert using sub-select, see #2528 +* Add convenience `left_outer_join()` method to query. +* Add `selected_columns` property to Select queries. +* Add `name` property to Alias instances. +* Fix regression in tests introduced by change to DataSet in 3.14.9. + +[View commits](https://github.com/coleifer/peewee/compare/3.14.9...3.14.10) + +## 3.14.9 + +* Allow calling `table_exists()` with a model-class, refs +* Improve `is_connection_usable()` method of `MySQLDatabase` class. +* Better support for VIEWs with `playhouse.dataset.DataSet` and sqlite-web. +* Support INSERT / ON CONFLICT in `playhosue.kv` for newer Sqlite. +* Add `ArrayField.contained_by()` method, a corollary to `contains()` and + the `contains_any()` methods. +* Support cyclical foreign-key relationships in reflection/introspection, and + also for sqlite-web. +* Add magic methods for FTS5 field to optimize, rebuild and integrity check the + full-text index. +* Add fallbacks in `setup.py` in the event distutils is not available. + +[View commits](https://github.com/coleifer/peewee/compare/3.14.8...3.14.9) ## 3.14.8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/docs/peewee/crdb.rst new/peewee-3.14.10/docs/peewee/crdb.rst --- old/peewee-3.14.8/docs/peewee/crdb.rst 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/docs/peewee/crdb.rst 2022-03-07 18:18:46.000000000 +0100 @@ -21,6 +21,40 @@ .. note:: CockroachDB requires the ``psycopg2`` (postgres) Python driver. +.. note:: + CockroachDB installation and getting-started guide can be + found here: https://www.cockroachlabs.com/docs/stable/install-cockroachdb.html + + +.. _crdb_ssl: + +SSL Configuration +^^^^^^^^^^^^^^^^^ + +SSL certificates are strongly recommended when running a Cockroach cluster. +Psycopg2 supports SSL out-of-the-box, but you may need to specify some +additional options when initializing your database: + +.. code-block:: python + + db = CockroachDatabase( + 'my_app', + user='root', + host='10.1.0.8', + sslmode='verify-full', # Verify the cert common-name. + sslrootcert='/path/to/root.crt') + + + # Or, alternatively, specified as part of a connection-string: + db = CockroachDatabase('postgresql://root:secret@host:26257/dbname' + '?sslmode=verify-full&sslrootcert=/path/to/root.crt' + '&options=--cluster=my-cluster-xyz') + +More details about client verification can be found on the `libpq docs <https://www.postgresql.org/docs/9.1/libpq-ssl.html>`_. + +Cockroach Extension APIs +^^^^^^^^^^^^^^^^^^^^^^^^ + The ``playhouse.cockroachdb`` extension module provides the following classes and helpers: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/docs/peewee/database.rst new/peewee-3.14.10/docs/peewee/database.rst --- old/peewee-3.14.8/docs/peewee/database.rst 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/docs/peewee/database.rst 2022-03-07 18:18:46.000000000 +0100 @@ -206,6 +206,10 @@ .. note:: CockroachDB requires the ``psycopg2`` (postgres) Python driver. +.. note:: + CockroachDB installation and getting-started guide can be + found here: https://www.cockroachlabs.com/docs/stable/install-cockroachdb.html + CRDB provides client-side transaction retries, which are available using a special :py:meth:`CockroachDatabase.run_transaction` helper-method. This method accepts a callable, which is responsible for executing any transactional @@ -234,6 +238,7 @@ For more information, see: * :ref:`CRDB extension documentation <crdb>` +* :ref:`SSL configuration with CockroachDB <crdb_ssl>` * :ref:`Arrays <pgarrays>` (postgres-specific, but applies to CRDB) * :ref:`JSON <pgjson>` (postgres-specific, but applies to CRDB) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/docs/peewee/playhouse.rst new/peewee-3.14.10/docs/peewee/playhouse.rst --- old/peewee-3.14.8/docs/peewee/playhouse.rst 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/docs/peewee/playhouse.rst 2022-03-07 18:18:46.000000000 +0100 @@ -568,14 +568,16 @@ Sqlcipher backend ----------------- -* Although this extention's code is short, it has not been properly - peer-reviewed yet and may have introduced vulnerabilities. +.. note:: + Although this extention's code is short, it has not been properly + peer-reviewed yet and may have introduced vulnerabilities. -Also note that this code relies on pysqlcipher_ and sqlcipher_, and -the code there might have vulnerabilities as well, but since these +Also note that this code relies on sqlcipher3_ (python bindings) and sqlcipher_, +and the code there might have vulnerabilities as well, but since these are widely used crypto modules, we can expect "short zero days" there. -.. _pysqlcipher: https://pypi.python.org/pypi/pysqlcipher3 +.. _sqlcipher3: https://pypi.python.org/pypi/sqlcipher3 +.. _pysqlcipher3: https://pypi.python.org/pypi/pysqlcipher3 .. _sqlcipher: http://sqlcipher.net sqlcipher_ext API notes @@ -584,7 +586,7 @@ .. py:class:: SqlCipherDatabase(database, passphrase, **kwargs) Subclass of :py:class:`SqliteDatabase` that stores the database - encrypted. Instead of the standard ``sqlite3`` backend, it uses pysqlcipher_: + encrypted. Instead of the standard ``sqlite3`` backend, it uses sqlcipher3_: a python wrapper for sqlcipher_, which -- in turn -- is an encrypted wrapper around ``sqlite3``, so the API is *identical* to :py:class:`SqliteDatabase`'s, except for object construction parameters: @@ -2044,7 +2046,7 @@ Hybrid attributes encapsulate functionality that operates at both the Python *and* SQL levels. The idea for hybrid attributes comes from a feature of the -`same name in SQLAlchemy <http://docs.sqlalchemy.org/en/improve_toc/orm/extensions/hybrid.html>`_. +`same name in SQLAlchemy <https://docs.sqlalchemy.org/en/14/orm/extensions/hybrid.html>`_. Consider the following example: .. code-block:: python @@ -3644,6 +3646,10 @@ DATABASE = 'postgresql://postgres:password@localhost:5432/my_database' + # If we want to exclude particular views from the automatic connection + # management, we list them this way: + FLASKDB_EXCLUDED_ROUTES = ('logout',) + app = Flask(__name__) app.config.from_object(__name__) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/docs/peewee/querying.rst new/peewee-3.14.10/docs/peewee/querying.rst --- old/peewee-3.14.8/docs/peewee/querying.rst 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/docs/peewee/querying.rst 2022-03-07 18:18:46.000000000 +0100 @@ -1979,4 +1979,4 @@ Foreign Keys and Joins ---------------------- -This section have been moved into its own document: :ref:`relationships`. +This section has been moved into its own document: :ref:`relationships`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/docs/requirements.txt new/peewee-3.14.10/docs/requirements.txt --- old/peewee-3.14.8/docs/requirements.txt 1970-01-01 01:00:00.000000000 +0100 +++ new/peewee-3.14.10/docs/requirements.txt 2022-03-07 18:18:46.000000000 +0100 @@ -0,0 +1 @@ +docutils<0.18 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/examples/analytics/app.py new/peewee-3.14.10/examples/analytics/app.py --- old/peewee-3.14.8/examples/analytics/app.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/examples/analytics/app.py 2022-03-07 18:18:46.000000000 +0100 @@ -1,10 +1,9 @@ """ Example "Analytics" app. To start using this on your site, do the following: -* Create a postgresql database with HStore support: +* Create a postgresql database: createdb analytics - psql analytics -c "create extension hstore;" * Create an account for each domain you intend to collect analytics for, e.g. @@ -29,7 +28,7 @@ from flask import Flask, Response, abort, g, request from peewee import * -from playhouse.postgres_ext import HStoreField, PostgresqlExtDatabase +from playhouse.postgres_ext import BinaryJSONField, PostgresqlExtDatabase # Analytics settings. # 1px gif. @@ -49,10 +48,7 @@ app = Flask(__name__) app.config.from_object(__name__) -database = PostgresqlExtDatabase( - DATABASE_NAME, - register_hstore=True, - user='postgres') +database = PostgresqlExtDatabase(DATABASE_NAME, user='postgres') class BaseModel(Model): @@ -76,8 +72,8 @@ title = TextField(default='') ip = CharField(default='') referrer = TextField(default='') - headers = HStoreField() - params = HStoreField() + headers = BinaryJSONField() + params = BinaryJSONField() @classmethod def create_from_request(cls, account, request): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/examples/anomaly_detection.py new/peewee-3.14.10/examples/anomaly_detection.py --- old/peewee-3.14.8/examples/anomaly_detection.py 1970-01-01 01:00:00.000000000 +0100 +++ new/peewee-3.14.10/examples/anomaly_detection.py 2022-03-07 18:18:46.000000000 +0100 @@ -0,0 +1,64 @@ +import math +from peewee import * + + +db = SqliteDatabase(':memory:') + +class Reg(Model): + key = TextField() + value = IntegerField() + + class Meta: + database = db + + +db.create_tables([Reg]) + +# Create a user-defined aggregate function suitable for computing the standard +# deviation of a series. [email protected]('stddev') +class StdDev(object): + def __init__(self): + self.n = 0 + self.values = [] + + def step(self, value): + self.n += 1 + self.values.append(value) + + def finalize(self): + if self.n < 2: + return 0 + mean = sum(self.values) / self.n + sqsum = sum((i - mean) ** 2 for i in self.values) + return math.sqrt(sqsum / (self.n - 1)) + + +values = [2, 3, 5, 2, 3, 12, 5, 3, 4, 1, 2, 1, -9, 3, 3, 5] + +Reg.create_table() +Reg.insert_many([{'key': 'k%02d' % i, 'value': v} + for i, v in enumerate(values)]).execute() + +# We'll calculate the mean and the standard deviation of the series in a common +# table expression, which will then be used by our query to find rows whose +# zscore exceeds a certain threshold. +cte = (Reg + .select(fn.avg(Reg.value), fn.stddev(Reg.value)) + .cte('stats', columns=('series_mean', 'series_stddev'))) + +# The zscore is defined as the (value - mean) / stddev. +zscore = (Reg.value - cte.c.series_mean) / cte.c.series_stddev + +# Find rows which fall outside of 2 standard deviations. +threshold = 2 +query = (Reg + .select(Reg.key, Reg.value, zscore.alias('zscore')) + .from_(Reg, cte) + .where((zscore >= threshold) | (zscore <= -threshold)) + .with_cte(cte)) + +for row in query: + print(row.key, row.value, round(row.zscore, 2)) + +db.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/examples/sqlite_fts_compression.py new/peewee-3.14.10/examples/sqlite_fts_compression.py --- old/peewee-3.14.8/examples/sqlite_fts_compression.py 1970-01-01 01:00:00.000000000 +0100 +++ new/peewee-3.14.10/examples/sqlite_fts_compression.py 2022-03-07 18:18:46.000000000 +0100 @@ -0,0 +1,64 @@ +# +# Small example demonstrating the use of zlib compression with the Sqlite +# full-text search extension. +# +import zlib + +from peewee import * +from playhouse.sqlite_ext import * + + +db = SqliteExtDatabase(':memory:') + +class SearchIndex(FTSModel): + content = SearchField() + + class Meta: + database = db + + [email protected]('zlib_compress') +def _zlib_compress(data): + if data is not None: + if isinstance(data, str): + data = data.encode('utf8') + return zlib.compress(data, 9) + [email protected]('zlib_decompress') +def _zlib_decompress(data): + if data is not None: + return zlib.decompress(data) + + +SearchIndex.create_table( + tokenize='porter', + compress='zlib_compress', + uncompress='zlib_decompress') + +phrases = [ + 'A faith is a necessity to a man. Woe to him who believes in nothing.', + ('All who call on God in true faith, earnestly from the heart, will ' + 'certainly be heard, and will receive what they have asked and desired.'), + ('Be faithful in small things because it is in them that your strength ' + 'lies.'), + ('Faith consists in believing when it is beyond the power of reason to ' + 'believe.'), + ('Faith has to do with things that are not seen and hope with things that ' + 'are not at hand.')] + +for phrase in phrases: + SearchIndex.create(content=phrase) + +# Use the simple ranking algorithm. +query = SearchIndex.search('faith things', with_score=True) +for row in query: + print(round(row.score, 2), row.content.decode('utf8')) + +print('---') + +# Use the Okapi-BM25 ranking algorithm. +query = SearchIndex.search_bm25('believe', with_score=True) +for row in query: + print(round(row.score, 2), row.content.decode('utf8')) + +db.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/peewee.py new/peewee-3.14.10/peewee.py --- old/peewee-3.14.8/peewee.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/peewee.py 2022-03-07 18:18:46.000000000 +0100 @@ -70,7 +70,7 @@ mysql = None -__version__ = '3.14.8' +__version__ = '3.14.10' __all__ = [ 'AnyField', 'AsIs', @@ -1306,6 +1306,13 @@ def __hash__(self): return hash(self._alias) + @property + def name(self): + return self._alias + @name.setter + def name(self, value): + self._alias = value + def alias(self, alias=None): if alias is None: return self.node @@ -2314,6 +2321,13 @@ def select_extend(self, *columns): self._returning = tuple(self._returning) + columns + @property + def selected_columns(self): + return self._returning + @selected_columns.setter + def selected_columns(self, value): + self._returning = value + @Node.copy def from_(self, *sources): self._from_list = list(sources) @@ -2325,6 +2339,9 @@ item = self._from_list.pop() self._from_list.append(Join(item, dest, join_type, on)) + def left_outer_join(self, dest, on=None): + return self.join(dest, JOIN.LEFT_OUTER, on) + @Node.copy def group_by(self, *columns): grouping = [] @@ -3323,6 +3340,10 @@ yield obj def table_exists(self, table_name, schema=None): + if is_model(table_name): + model = table_name + table_name = model._meta.table_name + schema = model._meta.schema return table_name in self.get_tables(schema=schema) def get_tables(self, schema=None): @@ -4032,6 +4053,18 @@ warnings.warn('Unable to determine MySQL version: "%s"' % version) return (0, 0, 0) # Unable to determine version! + def is_connection_usable(self): + if self._state.closed: + return False + + conn = self._state.conn + if hasattr(conn, 'ping'): + try: + conn.ping(False) + except Exception: + return False + return True + def default_values_insert(self, ctx): return ctx.literal('() VALUES ()') @@ -6121,7 +6154,11 @@ self.model._schema._database = database del self.table - # Apply any hooks that have been registered. + # Apply any hooks that have been registered. If we have an + # uninitialized proxy object, we will treat that as `None`. + if isinstance(database, Proxy) and database.obj is None: + database = None + for hook in self._db_hooks: hook(database) @@ -7172,6 +7209,9 @@ item = self._from_list.pop() self._from_list.append(Join(item, dest, join_type, on)) + def left_outer_join(self, dest, on=None, src=None, attr=None): + return self.join(dest, JOIN.LEFT_OUTER, on, src, attr) + def join_from(self, src, dest, join_type=JOIN.INNER, on=None, attr=None): return self.join(dest, join_type, on, src, attr) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/playhouse/dataset.py new/peewee-3.14.10/playhouse/dataset.py --- old/peewee-3.14.8/playhouse/dataset.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/playhouse/dataset.py 2022-03-07 18:18:46.000000000 +0100 @@ -27,7 +27,7 @@ class DataSet(object): - def __init__(self, url, **kwargs): + def __init__(self, url, include_views=False, **kwargs): if isinstance(url, Database): self._url = None self._database = url @@ -45,9 +45,11 @@ # Introspect the database and generate models. self._introspector = Introspector.from_database(self._database) + self._include_views = include_views self._models = self._introspector.generate_models( skip_invalid=True, literal_column_names=True, + include_views=self._include_views, **kwargs) self._migrator = SchemaMigrator.from_database(self._database) @@ -80,7 +82,14 @@ @property def tables(self): - return self._database.get_tables() + tables = self._database.get_tables() + if self._include_views: + tables += self.views + return tables + + @property + def views(self): + return [v.name for v in self._database.get_views()] def __contains__(self, table): return table in self.tables @@ -107,7 +116,8 @@ updated = self._introspector.generate_models( skip_invalid=True, table_names=dependencies, - literal_column_names=True) + literal_column_names=True, + include_views=self._include_views) self._models.update(updated) def get_table_dependencies(self, table): @@ -135,10 +145,7 @@ return self._database.execute_sql(sql, params, commit) def transaction(self): - if self._database.transaction_depth() == 0: - return self._database.transaction() - else: - return self._database.savepoint() + return self._database.atomic() def _check_arguments(self, filename, file_obj, format, format_dict): if filename and file_obj: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/playhouse/flask_utils.py new/peewee-3.14.10/playhouse/flask_utils.py --- old/peewee-3.14.8/playhouse/flask_utils.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/playhouse/flask_utils.py 2022-03-07 18:18:46.000000000 +0100 @@ -85,11 +85,51 @@ return default class FlaskDB(object): - def __init__(self, app=None, database=None, model_class=Model): + """ + Convenience wrapper for configuring a Peewee database for use with a Flask + application. Provides a base `Model` class and registers handlers to manage + the database connection during the request/response cycle. + + Usage:: + + from flask import Flask + from peewee import * + from playhouse.flask_utils import FlaskDB + + + # The database can be specified using a database URL, or you can pass a + # Peewee database instance directly: + DATABASE = 'postgresql:///my_app' + DATABASE = PostgresqlDatabase('my_app') + + # If we do not want connection-management on any views, we can specify + # the view names using FLASKDB_EXCLUDED_ROUTES. The db connection will + # not be opened/closed automatically when these views are requested: + FLASKDB_EXCLUDED_ROUTES = ('logout',) + + app = Flask(__name__) + app.config.from_object(__name__) + + # Now we can configure our FlaskDB: + flask_db = FlaskDB(app) + + # Or use the "deferred initialization" pattern: + flask_db = FlaskDB() + flask_db.init_app(app) + + # The `flask_db` provides a base Model-class for easily binding models + # to the configured database: + class User(flask_db.Model): + email = CharField() + + """ + def __init__(self, app=None, database=None, model_class=Model, + excluded_routes=None): self.database = None # Reference to actual Peewee database instance. self.base_model_class = model_class self._app = app self._db = database # dict, url, Database, or None (default). + self._excluded_routes = excluded_routes or () if app is not None: self.init_app(app) @@ -107,6 +147,9 @@ else: initial_db = self._db + if 'FLASKDB_EXCLUDED_ROUTES' in app.config: + self._excluded_routes = app.config['FLASKDB_EXCLUDED_ROUTES'] + self._load_database(app, initial_db) self._register_handlers(app) @@ -178,8 +221,12 @@ return self._model_class def connect_db(self): + if self._excluded_routes and request.endpoint in self._excluded_routes: + return self.database.connect() def close_db(self, exc): + if self._excluded_routes and request.endpoint in self._excluded_routes: + return if not self.database.is_closed(): self.database.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/playhouse/kv.py new/peewee-3.14.10/playhouse/kv.py --- old/peewee-3.14.8/playhouse/kv.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/playhouse/kv.py 2022-03-07 18:18:46.000000000 +0100 @@ -1,6 +1,7 @@ import operator from peewee import * +from peewee import sqlite3 from peewee import Expression from playhouse.fields import PickleField try: @@ -37,7 +38,10 @@ self._ordered = ordered self._database = database or SqliteExtDatabase(':memory:') self._table_name = table_name - if isinstance(self._database, PostgresqlDatabase): + support_on_conflict = (isinstance(self._database, PostgresqlDatabase) or + (isinstance(self._database, SqliteDatabase) and + self._database.server_version >= (3, 24))) + if support_on_conflict: self.upsert = self._postgres_upsert self.update = self._postgres_update else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/playhouse/postgres_ext.py new/peewee-3.14.10/playhouse/postgres_ext.py --- old/peewee-3.14.8/playhouse/postgres_ext.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/playhouse/postgres_ext.py 2022-03-07 18:18:46.000000000 +0100 @@ -42,6 +42,7 @@ HKEY = '->' HUPDATE = '||' ACONTAINS = '@>' +ACONTAINED_BY = '<@' ACONTAINS_ANY = '&&' TS_MATCH = '@@' JSONB_CONTAINS = '@>' @@ -229,6 +230,9 @@ def contains_any(self, *items): return Expression(self, ACONTAINS_ANY, ArrayValue(self, items)) + def contained_by(self, *items): + return Expression(self, ACONTAINED_BY, ArrayValue(self, items)) + class ArrayValue(Node): def __init__(self, field, value): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/playhouse/reflection.py new/peewee-3.14.10/playhouse/reflection.py --- old/peewee-3.14.8/playhouse/reflection.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/playhouse/reflection.py 2022-03-07 18:18:46.000000000 +0100 @@ -5,6 +5,7 @@ from collections import namedtuple from inspect import isclass import re +import warnings from peewee import * from peewee import _StringField @@ -490,6 +491,10 @@ @classmethod def from_database(cls, database, schema=None): + if isinstance(database, Proxy): + if database.obj is None: + raise ValueError('Cannot introspect an uninitialized Proxy.') + database = database.obj # Reference the proxied db obj. if CockroachDatabase and isinstance(database, CockroachDatabase): metadata = CockroachDBMetadata(database) elif isinstance(database, PostgresqlDatabase): @@ -680,12 +685,19 @@ database = self.metadata.database schema = self.schema + pending = set() + def _create_model(table, models): + pending.add(table) for foreign_key in database.foreign_keys[table]: dest = foreign_key.dest_table if dest not in models and dest != table: - _create_model(dest, models) + if dest in pending: + warnings.warn('Possible reference cycle found between ' + '%s and %s' % (table, dest)) + else: + _create_model(dest, models) primary_keys = [] columns = database.columns[table] @@ -732,7 +744,11 @@ params['model'] = 'self' else: dest_table = column.foreign_key.dest_table - params['model'] = models[dest_table] + if dest_table in models: + params['model'] = models[dest_table] + else: + FieldClass = DeferredForeignKey + params['rel_model_name'] = dest_table if column.to_field: params['field'] = column.to_field @@ -757,6 +773,9 @@ except ValueError: if not skip_invalid: raise + finally: + if table in pending: + pending.remove(table) # Actually generate Model classes. for table, model in sorted(database.model_names.items()): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/playhouse/shortcuts.py new/peewee-3.14.10/playhouse/shortcuts.py --- old/peewee-3.14.8/playhouse/shortcuts.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/playhouse/shortcuts.py 2022-03-07 18:18:46.000000000 +0100 @@ -174,6 +174,40 @@ return update_model_from_dict(model_class(), data, ignore_unknown) +def insert_where(cls, data, where=None): + """ + Helper for generating conditional INSERT queries. + + For example, prevent INSERTing a new tweet if the user has tweeted within + the last hour:: + + INSERT INTO "tweet" ("user_id", "content", "timestamp") + SELECT 234, 'some content', now() + WHERE NOT EXISTS ( + SELECT 1 FROM "tweet" + WHERE user_id = 234 AND timestamp > now() - interval '1 hour') + + Using this helper: + + cond = ~fn.EXISTS(Tweet.select().where( + Tweet.user == user_obj, + Tweet.timestamp > one_hour_ago)) + + iq = insert_where(Tweet, { + Tweet.user: user_obj, + Tweet.content: 'some content'}, where=cond) + + res = iq.execute() + """ + for field, default in cls._meta.defaults.items(): + if field.name in data or field in data: continue + value = default() if callable_(default) else default + data[field] = value + fields, values = zip(*data.items()) + sq = Select(columns=values).where(where) + return cls.insert_from(sq, fields) + + class ReconnectMixin(object): """ Mixin class that attempts to automatically reconnect to the database under diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/playhouse/sqlite_ext.py new/peewee-3.14.10/playhouse/sqlite_ext.py --- old/peewee-3.14.8/playhouse/sqlite_ext.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/playhouse/sqlite_ext.py 2022-03-07 18:18:46.000000000 +0100 @@ -468,6 +468,8 @@ set(_alphabet.upper()) | set((chr(26),))) _invalid_ascii = set(chr(p) for p in range(128) if chr(p) not in _alphanum) +del _alphabet +del _alphanum _quote_re = re.compile(r'(?:[^\s"]|"(?:\\.|[^"])*")+') @@ -697,6 +699,14 @@ return cls._fts_cmd('merge', rank=npages) @classmethod + def optimize(cls): + return cls._fts_cmd('optimize') + + @classmethod + def rebuild(cls): + return cls._fts_cmd('rebuild') + + @classmethod def set_pgsz(cls, pgsz): return cls._fts_cmd('pgsz', rank=pgsz) @@ -709,6 +719,10 @@ return cls._fts_cmd('delete-all') @classmethod + def integrity_check(cls, rank=0): + return cls._fts_cmd('integrity-check', rank=rank) + + @classmethod def VocabModel(cls, table_type='row', table=None): if table_type not in ('row', 'col', 'instance'): raise ValueError('table_type must be either "row", "col" or ' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/playhouse/test_utils.py new/peewee-3.14.10/playhouse/test_utils.py --- old/peewee-3.14.8/playhouse/test_utils.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/playhouse/test_utils.py 2022-03-07 18:18:46.000000000 +0100 @@ -11,7 +11,9 @@ logging.Handler.__init__(self, *args, **kwargs) def emit(self, record): - self.queries.append(record) + # Counts all entries logged to the "peewee" logger by execute_sql(). + if record.name == 'peewee': + self.queries.append(record) class count_queries(object): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/setup.py new/peewee-3.14.10/setup.py --- old/peewee-3.14.8/setup.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/setup.py 2022-03-07 18:18:46.000000000 +0100 @@ -2,10 +2,16 @@ import platform import sys import warnings -from distutils.command.build_ext import build_ext -from distutils.errors import CCompilerError -from distutils.errors import DistutilsExecError -from distutils.errors import DistutilsPlatformError +try: + from distutils.command.build_ext import build_ext + from distutils.errors import CCompilerError + from distutils.errors import DistutilsExecError + from distutils.errors import DistutilsPlatformError +except ImportError: + from setuptools._distutils.command.build_ext import build_ext + from setuptools._distutils.errors import CCompilerError + from setuptools._distutils.errors import DistutilsExecError + from setuptools._distutils.errors import DistutilsPlatformError from setuptools import setup from setuptools.extension import Extension @@ -56,8 +62,12 @@ def _have_sqlite_extension_support(): import shutil import tempfile - from distutils.ccompiler import new_compiler - from distutils.sysconfig import customize_compiler + try: + from distutils.ccompiler import new_compiler + from distutils.sysconfig import customize_compiler + except ImportError: + from setuptools.command.build_ext import customize_compiler + from setuptools.command.build_ext import new_compiler libraries = ['sqlite3'] c_code = ('#include <sqlite3.h>\n\n' @@ -151,6 +161,9 @@ ], license='MIT License', platforms=['any'], + project_urls={ + 'Documentation': 'http://docs.peewee-orm.com', + 'Source': 'https://github.com/coleifer/peewee'}, scripts=['pwiz.py'], zip_safe=False, cmdclass={'build_ext': _PeeweeBuildExt}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/dataset.py new/peewee-3.14.10/tests/dataset.py --- old/peewee-3.14.8/tests/dataset.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/dataset.py 2022-03-07 18:18:46.000000000 +0100 @@ -14,10 +14,11 @@ from playhouse.dataset import DataSet from playhouse.dataset import Table -from .base import db_loader -from .base import skip_if +from .base import IS_SQLITE_OLD from .base import ModelTestCase from .base import TestModel +from .base import db_loader +from .base import skip_if db = db_loader('sqlite') @@ -30,6 +31,7 @@ user = ForeignKeyField(User) content = TextField() timestamp = DateTimeField() + status = IntegerField(default=1) class Category(TestModel): name = TextField() @@ -69,6 +71,38 @@ users.insert(username='charlie') self.assertEqual(list(users), [{'id': 1, 'username': 'charlie'}]) + @skip_if(IS_SQLITE_OLD) + def test_with_views(self): + self.dataset.query('CREATE VIEW notes_public AS ' + 'SELECT content, timestamp FROM note ' + 'WHERE status = 1 ORDER BY timestamp DESC') + try: + self.assertTrue('notes_public' in self.dataset.views) + self.assertFalse('notes_public' in self.dataset.tables) + + users = self.dataset['user'] + with self.dataset.transaction(): + users.insert(username='charlie') + users.insert(username='huey') + + notes = self.dataset['note'] + for i, (ct, st) in enumerate([('n1', 1), ('n2', 2), ('n3', 1)]): + notes.insert(content=ct, status=st, user_id='charlie', + timestamp=datetime.datetime(2022, 1, 1 + i)) + + self.assertFalse('notes_public' in self.dataset) + + # Create a new dataset instance with views enabled. + dataset = DataSet(self.dataset._database, include_views=True) + self.assertTrue('notes_public' in dataset) + public = dataset['notes_public'] + self.assertEqual(public.columns, ['content', 'timestamp']) + self.assertEqual(list(public), [ + {'content': 'n3', 'timestamp': datetime.datetime(2022, 1, 3)}, + {'content': 'n1', 'timestamp': datetime.datetime(2022, 1, 1)}]) + finally: + self.dataset.query('DROP VIEW notes_public') + def test_item_apis(self): dataset = DataSet('sqlite:///:memory:') users = dataset['users'] @@ -145,7 +179,8 @@ note = self.dataset['note'] columns = sorted(note.columns) - self.assertEqual(columns, ['content', 'id', 'timestamp', 'user_id']) + self.assertEqual(columns, ['content', 'id', 'status', 'timestamp', + 'user_id']) category = self.dataset['category'] columns = sorted(category.columns) @@ -285,17 +320,20 @@ note.insert( content='note %s' % i, timestamp=datetime.date(2014, 1, i), + status=i, user_id='charlie') notes = sorted(note.all(), key=operator.itemgetter('id')) self.assertEqual(notes[0], { 'content': 'note 1', 'id': 1, + 'status': 1, 'timestamp': datetime.datetime(2014, 1, 1), 'user_id': 'charlie'}) self.assertEqual(notes[-1], { 'content': 'note 3', 'id': 3, + 'status': 3, 'timestamp': datetime.datetime(2014, 1, 3), 'user_id': 'charlie'}) @@ -396,7 +434,8 @@ note = self.dataset['note'] note_ts = datetime.datetime(2017, 1, 2, 3, 4, 5) - note.insert(content='foo', timestamp=note_ts, user_id='charlie') + note.insert(content='foo', timestamp=note_ts, user_id='charlie', + status=2) buf = StringIO() self.dataset.freeze(note.all(), 'json', file_obj=buf) @@ -404,6 +443,7 @@ 'id': 1, 'user_id': 'charlie', 'content': 'foo', + 'status': 2, 'timestamp': '2017-01-02 03:04:05'}]) note.delete(id=1) @@ -415,6 +455,7 @@ 'id': 1, 'user_id': 'charlie', 'content': 'foo', + 'status': 2, 'timestamp': note_ts}]) def test_table_column_creation(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/db_tests.py new/peewee-3.14.10/tests/db_tests.py --- old/peewee-3.14.8/tests/db_tests.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/db_tests.py 2022-03-07 18:18:46.000000000 +0100 @@ -502,6 +502,10 @@ self.assertTrue(self.database.table_exists(User._meta.table_name)) self.assertFalse(self.database.table_exists('nuggies')) + self.assertTrue(self.database.table_exists(User)) + class X(TestModel): pass + self.assertFalse(self.database.table_exists(X)) + def test_get_tables(self): tables = self.database.get_tables() required = set(m._meta.table_name for m in self.requires) @@ -730,6 +734,29 @@ with_manual_commit() self.assertFalse(db.in_transaction()) + def test_proxy_bind_ctx_callbacks(self): + db = Proxy() + class BaseModel(Model): + class Meta: + database = db + + class Hook(BaseModel): + data = BlobField() # Attaches hook to configure blob-type. + + self.assertTrue(Hook.data._constructor is bytearray) + + class CustomSqliteDB(SqliteDatabase): + sentinel = object() + def get_binary_type(self): + return self.sentinel + + custom_db = CustomSqliteDB(':memory:') + + with custom_db.bind_ctx([Hook]): + self.assertTrue(Hook.data._constructor is custom_db.sentinel) + + self.assertTrue(Hook.data._constructor is bytearray) + class Data(TestModel): key = TextField() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/model_sql.py new/peewee-3.14.10/tests/model_sql.py --- old/peewee-3.14.8/tests/model_sql.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/model_sql.py 2022-03-07 18:18:46.000000000 +0100 @@ -1,6 +1,7 @@ import datetime from peewee import * +from peewee import Alias from peewee import Database from peewee import ModelIndex @@ -59,6 +60,30 @@ query = query2.select(Category.name) self.assertSQL(query, 'SELECT "t1"."name" FROM "category" AS "t1"', []) + def test_selected_columns(self): + query = (Person + .select( + Person.first, + Person.last, + fn.COUNT(Note.id).alias('ct')) + .join(Note)) + f_first, f_last, f_ct = query.selected_columns + self.assertEqual(f_first.name, 'first') + self.assertTrue(f_first.model is Person) + self.assertEqual(f_last.name, 'last') + self.assertTrue(f_last.model is Person) + self.assertTrue(isinstance(f_ct, Alias)) + f_ct = f_ct.unwrap() + self.assertEqual(f_ct.name, 'COUNT') + f_nid, = f_ct.arguments + self.assertEqual(f_nid.name, 'id') + self.assertTrue(f_nid.model is Note) + + query.selected_columns = (Person.first,) + f_first, = query.selected_columns + self.assertEqual(f_first.name, 'first') + self.assertTrue(f_first.model is Person) + def test_where_coerce(self): query = Person.select(Person.last).where(Person.id == '1337') self.assertSQL(query, ( @@ -171,6 +196,19 @@ 'INNER JOIN "favorite" AS "t3" ON ("t3"."tweet_id" = "t1"."id")'), []) + query = Tweet.select(Tweet.id).left_outer_join(Favorite).switch(Tweet).left_outer_join(User) + self.assertSQL(query, ( + 'SELECT "t1"."id" FROM "tweet" AS "t1" ' + 'LEFT OUTER JOIN "favorite" AS "t2" ON ("t2"."tweet_id" = "t1"."id") ' + 'LEFT OUTER JOIN "users" AS "t3" ON ("t1"."user_id" = "t3"."id")'), []) + + query = Tweet.select(Tweet.id).left_outer_join(User).switch(Tweet).left_outer_join(Favorite) + self.assertSQL(query, ( + 'SELECT "t1"."id" FROM "tweet" AS "t1" ' + 'LEFT OUTER JOIN "users" AS "t2" ON ("t1"."user_id" = "t2"."id") ' + 'LEFT OUTER JOIN "favorite" AS "t3" ON ("t3"."tweet_id" = "t1"."id")'), + []) + def test_model_alias(self): TA = Tweet.alias() query = (User diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/postgres.py new/peewee-3.14.10/tests/postgres.py --- old/peewee-3.14.8/tests/postgres.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/postgres.py 2022-03-07 18:18:46.000000000 +0100 @@ -372,6 +372,9 @@ assertAM(T.contains('omega', 'delta')) assertAM(T.contains('??????'), am5) assertAM(T.contains('alpha', 'delta'), am) + assertAM(T.contained_by('alpha', 'beta', 'delta'), am2, am3) + assertAM(T.contained_by('alpha', 'beta', 'gamma', 'delta'), + am, am2, am3) # Check for any. assertAM(T.contains_any('beta'), am, am2) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/reflection.py new/peewee-3.14.10/tests/reflection.py --- old/peewee-3.14.8/tests/reflection.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/reflection.py 2022-03-07 18:18:46.000000000 +0100 @@ -574,6 +574,37 @@ ('n1', datetime.datetime(2018, 1, 1))]) +class TestCyclicalFK(BaseReflectionTestCase): + @requires_sqlite + def test_cyclical_fk(self): + # NOTE: this schema was provided by a user. + cursor = self.database.cursor() + cursor.executescript( + 'CREATE TABLE flow_run_state (id CHAR(36) NOT NULL, ' + 'flow_run_id CHAR(36) NOT NULL, ' + 'CONSTRAINT pk_flow_run_state PRIMARY KEY (id), ' + 'CONSTRAINT fk_flow_run_state__flow_run_id__flow_run ' + 'FOREIGN KEY(flow_run_id) REFERENCES flow_run (id) ' + 'ON DELETE cascade); ' + 'CREATE TABLE flow_run (id CHAR(36) NOT NULL, ' + 'state_id CHAR(36) NOT NULL, ' + 'CONSTRAINT pk_flow_run PRIMARY KEY (id), ' + 'CONSTRAINT fk_flow_run__state_id__flow_run_state ' + 'FOREIGN KEY(state_id) REFERENCES flow_run_state (id) ' + 'ON DELETE SET NULL);') + M = self.introspector.generate_models() + FRS = M['flow_run_state'] + FR = M['flow_run'] + self.assertEqual(sorted(FR._meta.fields), ['id', 'state']) + self.assertEqual(sorted(FRS._meta.fields), ['flow_run', 'id']) + self.assertTrue(isinstance(FR.id, CharField)) + self.assertTrue(isinstance(FR.state, ForeignKeyField)) + self.assertTrue(FR.state.rel_model is FRS) + self.assertTrue(isinstance(FRS.id, CharField)) + self.assertTrue(isinstance(FRS.flow_run, ForeignKeyField)) + self.assertTrue(FRS.flow_run.rel_model is FR) + + class Event(TestModel): key = TextField() timestamp = DateTimeField(index=True) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/regressions.py new/peewee-3.14.10/tests/regressions.py --- old/peewee-3.14.8/tests/regressions.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/regressions.py 2022-03-07 18:18:46.000000000 +0100 @@ -1256,11 +1256,15 @@ id = UUIDField(primary_key=True, default=uuid.uuid4) key = TextField() +class CharPKKV(TestModel): + id = CharField(primary_key=True) + key = TextField() + value = IntegerField(default=0) -class TestBulkUpdateUUIDPK(ModelTestCase): - requires = [UUIDReg] +class TestBulkUpdateNonIntegerPK(ModelTestCase): @skip_if(sys.version_info[0] == 2) + @requires_models(UUIDReg) def test_bulk_update_uuid_pk(self): r1 = UUIDReg.create(key='k1') r2 = UUIDReg.create(key='k2') @@ -1272,6 +1276,22 @@ self.assertEqual(r1_db.key, 'k1-x') self.assertEqual(r2_db.key, 'k2-x') + @requires_models(CharPKKV) + def test_bulk_update_non_integer_pk(self): + a, b, c = [CharPKKV.create(id=c, key='k%s' % c) for c in 'abc'] + a.key = 'ka-x' + a.value = 1 + b.value = 2 + c.key = 'kc-x' + c.value = 3 + CharPKKV.bulk_update((a, b, c), (CharPKKV.key, CharPKKV.value)) + + data = list(CharPKKV.select().order_by(CharPKKV.id).tuples()) + self.assertEqual(data, [ + ('a', 'ka-x', 1), + ('b', 'kb', 2), + ('c', 'kc-x', 3)]) + class TestSaveClearingPK(ModelTestCase): requires = [User, Tweet] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/shortcuts.py new/peewee-3.14.10/tests/shortcuts.py --- old/peewee-3.14.8/tests/shortcuts.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/shortcuts.py 2022-03-07 18:18:46.000000000 +0100 @@ -737,3 +737,47 @@ # In the main thread the original database has not been altered. self.assertTrue(M._meta.database is d1) + + +class TIW(TestModel): + key = CharField() + value = IntegerField(default=0) + extra = IntegerField(default=lambda: 1) + + +class TestInsertWhere(ModelTestCase): + requires = [User, Tweet, TIW] + + def test_insert_where(self): + ua, ub = [User.create(username=n) for n in 'ab'] + + def _insert_where(user, content): + cond = (Tweet.select() + .where(Tweet.user == user, Tweet.content == content)) + where = ~fn.EXISTS(cond) + iq = insert_where(Tweet, { + Tweet.user: user, + Tweet.content: content}, + where=where) + return 1 if iq.execute() else 0 + + self.assertEqual(_insert_where(ua, 't1'), 1) + self.assertEqual(_insert_where(ua, 't2'), 1) + self.assertEqual(_insert_where(ua, 't1'), 0) + self.assertEqual(_insert_where(ua, 't2'), 0) + self.assertEqual(_insert_where(ub, 't1'), 1) + self.assertEqual(_insert_where(ub, 't2'), 1) + + def test_insert_where_defaults(self): + TIW.create(key='k1', value=1, extra=2) + def _insert_where(key): + where = ~fn.EXISTS(TIW.select().where(TIW.key == key)) + iq = insert_where(TIW, {TIW.key: key}, where) + return 1 if iq.execute() else 0 + + self.assertEqual(_insert_where('k2'), 1) + self.assertEqual(_insert_where('k1'), 0) + self.assertEqual(_insert_where('k2'), 0) + tiw = TIW.get(TIW.key == 'k2') + self.assertEqual(tiw.value, 0) + self.assertEqual(tiw.extra, 1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/sql.py new/peewee-3.14.10/tests/sql.py --- old/peewee-3.14.8/tests/sql.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/sql.py 2022-03-07 18:18:46.000000000 +0100 @@ -3,6 +3,7 @@ from peewee import * from peewee import Expression +from peewee import Function from peewee import query_to_string from .base import BaseTestCase @@ -44,6 +45,29 @@ 'SELECT "t1"."username", "t1"."is_admin", "t1"."is_active", ' '"t1"."id" FROM "users" AS "t1"'), []) + def test_selected_columns(self): + query = (User + .select(User.c.id, User.c.username, fn.COUNT(Tweet.c.id)) + .join(Tweet, JOIN.LEFT_OUTER, + on=(User.c.id == Tweet.c.user_id))) + # NOTE: because of operator overloads for equality we have to test by + # asserting the attributes of the selected cols. + c_id, c_username, c_ct = query.selected_columns + self.assertEqual(c_id.name, 'id') + self.assertTrue(c_id.source is User) + self.assertEqual(c_username.name, 'username') + self.assertTrue(c_username.source is User) + self.assertTrue(isinstance(c_ct, Function)) + self.assertEqual(c_ct.name, 'COUNT') + c_tid, = c_ct.arguments + self.assertEqual(c_tid.name, 'id') + self.assertTrue(c_tid.source is Tweet) + + query.selected_columns = (User.c.username,) + c_username, = query.selected_columns + self.assertEqual(c_username.name, 'username') + self.assertTrue(c_username.source is User) + def test_select_explicit_columns(self): query = (Person .select() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/peewee-3.14.8/tests/sqlite.py new/peewee-3.14.10/tests/sqlite.py --- old/peewee-3.14.8/tests/sqlite.py 2021-10-28 16:29:02.000000000 +0200 +++ new/peewee-3.14.10/tests/sqlite.py 2022-03-07 18:18:46.000000000 +0100 @@ -1272,6 +1272,12 @@ ['merge', 4]) FTS5Test.merge(4) # Runs without error. + FTS5Test.insert_many([{'title': 'k%08d' % i, 'data': 'v%08d' % i} + for i in range(100)]).execute() + + FTS5Test.integrity_check(rank=0) + FTS5Test.optimize() + def test_create_table_options(self): class Test1(FTS5Model): f1 = SearchField()
