Releases: coleifer/peewee
Releases · coleifer/peewee
Release list
4.4.0
In which we learn to migrate (somewhat).
- Add
playhouse.migrationsfor running migration scripts. Migrations are python files definingup(migrator, db)and optionallydown(...). Migrations are applied in numeric order a-la Django, and stored by name in a history table. CLI viapwmigrateacceptingstatus,up,down,initial,create,generate,fakeanddiff. To run from python,migrations.run(db). - Add basic
playhouse.schema_difffor comparing models against the schema and reporting differences (tables to create, columns added or removed, indexes added or removed). - Allow adding column to existing table as
NOT NULLwith migrator, which allows skipping the 3-step process of add nullable, populate default, set not null. db_url.connect()raisesValueErrorfor a url with no database name, e.g.postgres://dbname(two slashes readsdbnameas the host).- Add a
SchemaMigrator.migration_context()helper for wrapping migrations. This was wanted for SQLite in order to disable FK pragma, which could trigger cascading deletes while recreating tables. - Allow
SchemaMigrator.from_database()to support database proxies. - Add support for newer SQLite ALTER TABLE functionality from 3.53.0.
- Do not allow
delete()method to be called on a model instance.Model.delete()is a classmethod for constructing aDELETEquery, andmodel.delete_instance()has always been the correct path for deleting a model instance. This new check just ensures that a new user cannot accidentally delete their whole table by using the class-version from an instance. Fixes #2277. - Server-side cursors opened inside a transaction on psycopg3 are no longer declared
WITH HOLD. They stream and are scoped to the transaction, rather than spooling their remaining rows server-side at commit. scalar()applies aLIMIT 1viafirst(), rather than running the query unbounded and reading the first value. The query itself is not mutated, the limit is applied only on an internal copy, refs #3068.- Ensure
JSONFieldworks when proxy is already initialized. Thanks @NotAFlightRisk, refs #3070. commit()/rollback()on a closed db will raise rather than silently open a new connection.

4.3.0
Backwards-incompatible:
- Specify
requires-python >= 3.8. I've been putting off committing to anything like this, since technically we still work on 3.7, but 3.8 is the minimum we run on CI so it felt correct. - Replace
docidimplicit primary key on legacyFTSModel(FTS4) withrowid, which is equivalent. Usingdocidpresents no benefit and switching torowidmakes operations more consistent. Users have a couple options when updating:- Explicitly add
docid = DocIDField()to your FTSModel classes. - Update your code, replacing
docidwithrowid. The underlying data does not require a migration, as docid was just an alias for rowid.
- Explicitly add
- When a RETURNING-clause insert of a single row inserts nothing, e.g. a conflict was ignored,
execute()returnsNoneon every backend.
Improvements:
- Connection pools roll back transactions left open on check-in.
- Pooled Postgres probes idle connections with
SELECT 1and discards dead ones, matching the MySQL pool's ping. Previously a connection terminated server-side while parked in the pool was handed out and failed on first use. close_pool()in pwasyncio no longer spins the event loop on Python 3.13+ attempting to reclaim connections in use, and pool creation is now bounded byacquire_timeout. Connections terminated during shutdown are detected as stale and discarded at the next checkout.JSONFieldnegative path indexes render as$[last]/$[last-n]on MySQL/MariaDB. Previously the sqlite-only$[#-n]form was emitted, which MariaDB evaluates to NULL (overwriting the column when used withset()) and MySQL rejects as an invalid path.JSONFieldmutators (set(),insert(), etc) store Python booleans as json true/false instead of the driver's 0/1, so values written bycreate()and by mutators compare consistently. Floats on MySQL/MariaDB likewise take their json text form, as MariaDB reformats driver floats in a way that breaks equality against the stored document.- Reflection/pwiz map MySQL JSON columns to the core
JSONFieldinstead of emittingfrom playhouse.mysql_ext import *for a re-exported field. playhouse.pwasynciologs to thepeewee.pwasynciologger rather thanplayhouse.pwasyncio.- Fix
datasetfreeze/thaw of NULL blob and datetime values. Empty CSV cells now import as NULL for non-text fields. - Lateral joins honor a user-supplied
on=predicate instead of silently replacing it withtrue, and default toON truewhenon=is omitted. - The SQLite FTS
contentoption must be a Model or table-name string. Passing a Field now raisesImproperlyConfigured: it generated DDL that fts5 rejects outright and that fts4 silently truncated to the table name. - Fix
FTS5Model.VocabModel(): term/col/offset were declared as virtual fields and omitted from default SELECTs, the instance-type model had the wrong column set, all three table-types shared one default table name, and the generated class was cached with whatever database was bound at first call. Vocab models are now built fresh per call with real fields, correct columns and per-type default names. - Add
FTS5Model.web_query(), which translates the query syntax users expect from a search box (quoted phrases, AND/OR/NOT,-exclusion,column:filters and parentheses) into an FTS5 query. Anything else is searched as text, socovid-19orc++need no escaping, and the translation is always a valid query. The parser lives in the newplayhouse.fts_parsermodule. Use it with search:Doc.search(Doc.web_query(user_input)). - Add
FTS5Model.delete_command(), which removes a row using the fts5deletecommand. This is how rows are removed from external-content and contentless tables, which need the originally-indexed values supplied back to them: sqlite treats an omitted column as NULL, and values that do not match what was indexed leave stale entries behind (undetectably so on a contentless table). Peewee therefore requires a value for every indexed column; passNonewhere NULL was indexed. The command exists only for those two configurations - default-storage andcontentless_delete=1tables reject it and use ordinaryDELETE. - Add support for cysqlite's sick table func decorator syntax.
- Better behavior for INSERT when
as_rowcount()is specified, along with proper return of all parts of a composite PK instead of just the 1st column. last_insert_id()is implemented once onDatabase, with backends overriding_last_insert_rowid()where the driver differs. APSW and the MariaDB connector inherit composite primary-key support as a result, having previously returned only the first column.- Don't apply field kwargs to barefield instances w/reflection, #3064.

4.2.6
Just tidying up a loose end from the 4.2.4/4.2.5 -- a missed outer join is now cached as an absent relation instead of being written through the foreign-key descriptor. The fk id on the source instance keeps the column's value (previously it was overwritten with None), and accessing the attribute on a non-null fk returns None instead of raising DoesNotExist.

4.2.5
4.2.4
- Fix derived table joined in an expression subquery losing its FROM alias.
- Fix default
Model.select()used as a FROM/JOIN source reduced to its pk. - Fix compound/subquery SELECT-list column emitting a phantom alias.
- Fix
fn.EXISTS(compound)double-parenthesizing. - Fix
x.in_(ValuesList(...))dropping parens aroundVALUES. - Fix two-FK
.join(on=...)mis-attaching rows when the fk is on the rhs. - Fix
ON CONFLICT ... DO NOTHINGdropping the target/where/constraint.

4.2.3
Bug hunt wrapped up!
These were all pretty far out there on the edge of edge-cases. Things are looking solid all around.
- Fix a compound select (
UNION/INTERSECT/EXCEPT) used as a correlated subquery emitting a phantom alias for the correlated outer table in every branch but the left-most, producing invalid SQL (e.g.no such column: t4.id). The right-hand branch renders in a fresh alias scope that no longer resolved the outer source's existing alias, it now inherits the enclosing scope's aliases while still assigning fresh aliases to its own sources. - Fix full-text search
weightspassed as adictbeing mis-applied to the wrong columns. For FTS3/4 the implicitdocidprimary-key was included when building the weight list, shifting every column by one (raisingIndexErrorwith the Python ranking UDF, silently mis-scoring with the Cython one), for FTS5,UNINDEXEDcolumns were skipped even thoughbm25()weights are positional across all columns. The list form ofweightswas unaffected. - Fix
.cte()clearing the source query's CTE list in place: converting a query that carried awith_cte(...)clause into a CTE stripped the clause from that query, so reusing it afterward referenced an undeclared CTE. The query is now cloned before its CTE list is reset. - Fix
Table.select()with no arguments on aTabledeclared without columns emitting an empty projection (SELECT FROM ...) instead ofSELECT *. - Fix
Table.insert(select_query)with nocolumnsraisingTypeErrorinstead of renderingINSERT INTO t SELECT .... - Fix the MySQL migrator dropping a foreign key's
ON DELETE/ON UPDATEaction whenadd_not_null()orrename_column()rebuilds the constraint, silently downgrading e.g.CASCADEtoRESTRICT. The actions reported byget_foreign_keys()are now carried through to the rebuilt constraint. - Fix the legacy
postgres_extJSONcontains/contained_by/concatraisingAttributeError, andremove()silently rewriting the entire column, when applied to a.path()-chained lookup (e.g.Model.data['a'].path('b')). All four now resolve the root field and full path via_resolve_root(), matching the siblingset/replace/insert/append/updatemutators. - Correct the
postgres_ext.JSONFielddocs: thejson-column field does not support thejsonb-based mutation/concatenation builders (they raiseProgrammingError), so the misleading "Postgres casts implicitly" claim was removed and new code is steered to the built-inJSONField. - Fix the SQLite migrator treating a bare table-level
UNIQUE (a, b)constraint as a column when rebuilding a table (add_not_null,drop_column, ...), raisingno column named UNIQUE;uniqueis now recognized as a constraint. - Fix the SQLite migrator's table rebuild corrupting the
CREATE TABLEkeywords for a table whose name is a case-insensitive substring of them (e.g.ab,t,tab) -- the table-name substitution is now anchored to the trailing name token.
4.2.2
- Change
Field.__hash__again... fml. Use(model_cls, field name). - Fix
Metadata.remove_ref()removing the wrong foreign-key when a model has multiple foreign-keys to the same target, aslist.remove()matched the first entry via the overloadedField.__eq__. - Fix a scalar subquery nested inside a function,
CaseorCastcollapsing to its alias in anUPDATE ... SETvalue and inON CONFLICT DO UPDATE, asqualify_names()wrapped the value atSCOPE_COLUMN. - Fix
namedtuples()on a query-builder (Table) query raisingValueErrorwhen a column name is not a valid identifier. The plainNamedTupleCursorWrappernow passesrename=True, matching the model path. - Fix outer joins in a joined model graph not hydrating a missing related object as
None, so accessing the attribute raisedAttributeError. The outer-join test had regressed toendswith('OUTER')(never true). It now also recognizesFULL JOINandLEFT JOIN LATERAL. - Fix
ModelSelect.select_extend()mutating its receiver's default-projection flag, so a baseModel.select()reused as a subquery stopped collapsing to its primary key. It now flags the returned clone, matchingselect(). - Fix
distinct(True)anddistinct(False)not clearing a priordistinct(*columns), so the query kept renderingDISTINCT ON (...)instead of a plainDISTINCTor no distinct at all. - Fix Postgres
get_indexes()shredding an expression index whose key contains a comma, e.g.COALESCE(a, 0)split into two bogus columns. It joined the per-key definitions into a comma-delimited string and split on the comma. It now reads the key array directly. - Fix an empty insert (
Model.insert(),insert({})) emittingDEFAULT VALUESand dropping python-side field defaults, inconsistent with a partial insert which backfills them. A model with no python defaults still usesDEFAULT VALUES.

4.2.1
4.2.0
- Add django-style filter lookups:
contains,startswith,endswith,between,is_null,not_inandiregexp. - Fix SQLite index value inlining to apply properly.
- Fix
PostgresqlDatabase(isolation_level=...)having no effect on transactions. Previously onlyatomic(isolation_level=...)worked. - Fix
Ordering.collate()dropping thenulls=ordering. - Fix double-escaping of backticks in MySQL
get_indexes(). - Honor the
windows=parameter of theSelectconstructor. - Remove vestigial Python 2 compat (
reraise(),__div__,__nonzero__) and assorted dead internal code. - Remove
TimestampField.local_to_utc()andTimestampField.utc_to_local(). Select.columns()no longer accepts and ignores keyword arguments.- Remove unused
Metadata.get_rel_for_model(). - Fix
SelectBase.exists()ignoring itsdatabaseargument. - Fix
CursorWrapperindexing:cursor[n]raised IndexError for uncached rows andcursor[0]fetched the entire result set. - Fix
.namedtuples()crashing on selected columns that are not valid Python identifiers. - Preserve
materialized=when compounding CTEs viaunion()/union_all(). - Fix
ManyToManyFieldreads when the through-model foreign keys use the'!'backref sentinel. - Fix connection pooling with the
mariadbconnector - pooled connections were discarded on every checkout. - Fix
sqliteqstop()to drain the write queue and return True. - Fix apsw aggregate registration binding every name to the last-registered aggregate class.
- Fix two
NameErrors incysqlite_ext:blob_open()andprogress(). - Fix pwiz emitting an invalid
attr=keyword instead ofon_delete/on_updatefor reflected foreign keys. - Fix
datasetinfinite loop on self-referential foreign keys, crash on headerless CSV import,thaw()validating against export rather than import formats, and the importer mutating live model metadata. - Fix
model_to_dictto honoronly=/exclude=for many-to-many fields, fixresolve_multimodel_queryon queries with narrowed selections. - Fix
signals.Model.save(True)reportingcreated=Falsewhenforce_insertis passed positionally. - Fix
CompressedFieldcrashing onstrvalues. - Fix psycopg3 server-side cursors (missing
withhold) and CockroachDBrun_transactionretry detection under psycopg3. - Async queries are now logged to the
peeweelogger. - Remove dead code and unused imports throughout
playhouse; remove the broken, unusedget_current_url/get_next_urlhelpers fromflask_utils. - Fix
delete_instance(recursive=True)failing to cascade to the children of a model reachable through both nullable and non-nullable foreign-keys. - Fix subqueries losing their parentheses when used as a CASE value inside a single-argument function call, e.g.
fn.SUM(Case(...)). - Fix plain-
Tableinserts on returning-clause databases binding the primary-key name as a parameter and returning None instead of the new id. CompositeKeycomparisons raiseValueErrorwhen the value's length does not match the key, rather than silently matching on a prefix.- Async: connection-acquisition errors are translated to peewee exception types, matching query execution.
- Fix
FieldAlias.modelto reference the model alias rather than the aliased model; alias-rooted join queries no longer construct and discard a spurious instance of the aliased model for every result row. - Fix
playhouse.postgres_ext.JSONFieldcreatingjsonbcolumns after the core postgres backend began mapping the JSON field-type to JSONB; its DDL isjsonagain, and json-vs-jsonb function selection for chained lookups now follows the field's declared datatype. - Unaliased expressions in join queries now hydrate using the same cleaned attribute name as flat queries (e.g.
COUNTrather thanCOUNT(1). Field.__hash__is keyed on the model's schema and table-name rather than its class name, so same-named model classes (factories, separate modules, schema-per-tenant layouts) no longer collide in field-keyed registries such as backrefs; redefining or re-importing a model in place still replaces its entries.- Fix
UnboundLocalErrorwhen joining from a model-less source to a model, e.g.join_from(cte, SomeModel, on=...); the joined instance is stored in the source's row dict, keyed by the model name. BlobField,CompressedFieldand thesqlite_udf.gzip()function encodestrvalues using utf-8 instead ofraw_unicode_escape. Behavior change for non-ASCII strings: characters above the latin-1 range are no longer mangled into literal escape sequences, but blobs written from non-ASCII strings by earlier versions will not compare equal to newly-written ones.
Massive bug hunt and patch release. Should be all set, going to let these fixes simmer for a bit.

4.1.2
- Ensure quotes escaped in SQLite introspection methods, thanks @greymoth-jp for reporting and the initial patch.
- Allow TimestampField to accept an iso-formatted str.
- Add key-existence predicates (
has_key,has_keys,has_any_keys) to the coreJSONFieldon SQLite, implemented withjson_type(). - Add containment predicates (
contains,contained_by) to the coreJSONFieldon SQLite via a registered_pw_json_containsUDF that emulates Postgres'@>semantics (structural, level-aligned). The coreJSONFieldnow has full predicate parity across SQLite, Postgres, and MySQL/MariaDB.
Good ol' blimby!

