NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #498 most downloaded on PyPI
a little orm
Last release 7 days ago
27 Sep 2026
Ships fairly regularly
a new release about every 2 weeks
Nearly every release is documented
notes for 60 of the last 60 stable releases
1 version withdrawn
withdrawn after publishing
16 years old
222 releases · first in 2010
Don't cache None on unselected FK join trees, see #3083 .
None on unselected FK join trees, see #3083.exists() wraps the query in EXISTS(...) rather than replacing its columns, fixing queries that order by or filter on a selected alias, and queries on MySQL that select two columns w/the same name.One column per quarter.
Couple little things for EnumField .
Couple little things for EnumField.
EnumField and IntEnumField validate member value types (str and int respectively) at field construction.EnumField and IntEnumField default choices to (value, name) pairs derived from the enum.EnumField. Generated migrations will emit the storage field type (e.g. IntegerField) rather than EnumField().MySQL and MariaDB connections no longer set sql_mode . Peewee had been setting PIPES_AS_CONCAT , but the param replaced the server's mode rather than
Backwards-incompatible:
sql_mode. Peewee had been setting PIPES_AS_CONCAT, but the param replaced the server's mode rather than adding to it, which silently disabled STRICT_TRANS_TABLES (etc). Going forward Peewee will not modify sql_mode by default and use CONCAT() rather than || for MySQL/MariaDB.utf8mb4 instead of utf8, which is an alias for utf8mb3 and cannot store 4-byte characters.sequence= is now qualified with the model's Meta.schema. If the name you pass already contains a dot it is treated as fully-qualified and used as-is, so sequence='other.seq' is unaffected by Meta.schema. Also, sequence_exists() accepts a schema-qualified name.BEGIN, COMMIT and ROLLBACK are issued directly on a cursor rather than through execute_sql(), so they are no longer debug-logged, do not fire query hooks and are not counted by count_queries(). This matches Postgres and MySQL.atomic() and transaction() on a closed database with autoconnect=False now raise InterfaceError on Postgres and MySQL, as they already did on SQLite (and as queries do). Previously begin() opened a connection regardless of autoconnect.Improvements:
SqliteDatabase(..., lock_type=...) to set the default locking strategy for transactions that do not specify one, e.g. lock_type='IMMEDIATE'.get_tables(), get_columns(), get_indexes(), get_primary_keys(), get_foreign_keys(), get_views()) with no schema now follows the search path via current_schema() instead of assuming public, matching MySQL's DATABASE() behavior.Runner(db, schema=...) and pwmigrate --schema for running migrations against a specific schema. The history table lives in that schema, so each schema tracks its own applied set and one set of migration files can be run against any number of schemas.SchemaMigrator(db, schema=...), which qualifies every table name w/the given schema (or database on MySQL). Not supported for SQLite, as the table-rebuild rewrites DDL straight from sqlite_master.websearch=True to TSVectorField.match() and Match() to parse the query using websearch_to_tsquery() (pg 11+). Accepts user input without raising (quoted phrases, or, and -negation).Database.query_hooks, a list of callables invoked after every query with a QueryEvent named tuple (sql, params, duration, exception), on success and failure both. No timing overhead when the list is empty.Database.after_commit(fn), which runs a callable after the outermost transaction commits. Hook is discarded on rollback and runs immediately if no transaction is active. Use for, e.g., writing a row PK to a task queue.EnumField and IntEnumField to playhouse.fields, storing member.value and returning the member, rejecting unknown values on write and comparison. to_pydantic() maps any field with an enum_class attribute to the enum itself, so schemas validate membership.Database.dispose() / PooledDatabase.dispose() for discarding and resetting local connection state, e.g. in a child process after fork().Model.bind_ctx() and Database.bind_ctx() can be used as decorators, like the other peewee context-managers. Previously the decorator form raised TypeError: '_BoundModelsContext' object is not callable.TimeField supports utc offsets like DateTimeField, parsing e.g. '11:12:13+02:00' to an aware datetime.time instead of returning str. Only sqlite stores the offset. The postgres/mysql drivers drop it on write.In which we learn to migrate (somewhat).
In which we learn to migrate (somewhat).
playhouse.migrations for running migration scripts. Migrations are python files defining up(migrator, db) and optionally down(...). Migrations are applied in numeric order a-la Django, and stored by name in a history table. CLI via pwmigrate accepting status, up, down, initial, create, generate, fake and diff. To run from python, migrations.run(db).playhouse.schema_diff for comparing models against the schema and reporting differences (tables to create, columns added or removed, indexes added or removed).NOT NULL with migrator, which allows skipping the 3-step process of add nullable, populate default, set not null.db_url.connect() raises ValueError for a url with no database name, e.g. postgres://dbname (two slashes reads dbname as the host).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.SchemaMigrator.from_database() to support database proxies.delete() method to be called on a model instance. Model.delete() is a classmethod for constructing a DELETE query, and model.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.WITH HOLD. They stream and are scoped to the transaction, rather than spooling their remaining rows server-side at commit.scalar() applies a LIMIT 1 via first(), 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.JSONField works when proxy is already initialized. Thanks @NotAFlightRisk, refs #3070.commit() / rollback() on a closed db will raise rather than silently open a new connection.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 minimu
Backwards-incompatible:
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.docid implicit primary key on legacy FTSModel (FTS4) with rowid, which is equivalent. Using docid presents no benefit and switching to rowid makes operations more consistent. Users have a couple options when updating:
docid = DocIDField() to your FTSModel classes.docid with rowid. The underlying data does not require a migration, as docid was just an alias for rowid.execute() returns None on every backend.Improvements:
SELECT 1 and 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 by acquire_timeout. Connections terminated during shutdown are detected as stale and discarded at the next checkout.JSONField negative 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 with set()) and MySQL rejects as an invalid path.JSONField mutators (set(), insert(), etc) store Python booleans as json true/false instead of the driver's 0/1, so values written by create() 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.JSONField instead of emitting from playhouse.mysql_ext import * for a re-exported field.playhouse.pwasyncio logs to the peewee.pwasyncio logger rather than playhouse.pwasyncio.dataset freeze/thaw of NULL blob and datetime values. Empty CSV cells now import as NULL for non-text fields.on= predicate instead of silently replacing it with true, and default to ON true when on= is omitted.content option must be a Model or table-name string. Passing a Field now raises ImproperlyConfigured: it generated DDL that fts5 rejects outright and that fts4 silently truncated to the table name.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.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, so covid-19 or c++ need no escaping, and the translation is always a valid query. The parser lives in the new playhouse.fts_parser module. Use it with search: Doc.search(Doc.web_query(user_input)).FTS5Model.delete_command(), which removes a row using the fts5 delete command. 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; pass None where NULL was indexed. The command exists only for those two configurations - default-storage and contentless_delete=1 tables reject it and use ordinary DELETE.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 on Database, 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.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 forei
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.
Fix longstanding bug where anonymous sub-select may keep a stale id() -based hash after clone() .
id()-based hash after clone().Fix derived table joined in an expression subquery losing its FROM alias.
Model.select() used as a FROM/JOIN source reduced to its pk.fn.EXISTS(compound) double-parenthesizing.x.in_(ValuesList(...)) dropping parens around VALUES..join(on=...) mis-attaching rows when the fk is on the rhs.ON CONFLICT ... DO NOTHING dropping the target/where/constraint.These were all pretty far out there on the edge of edge-cases. Things are looking solid all around.
These were all pretty far out there on the edge of edge-cases. Things are looking solid all around.
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.weights passed as a dict being mis-applied to the wrong columns. For FTS3/4 the implicit docid primary-key was included when building the weight list, shifting every column by one (raising IndexError with the Python ranking UDF, silently mis-scoring with the Cython one), for FTS5, UNINDEXED columns were skipped even though bm25() weights are positional across all columns. The list form of weights was unaffected..cte() clearing the source query's CTE list in place: converting a query that carried a with_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.Table.select() with no arguments on a Table declared without columns emitting an empty projection (SELECT FROM ...) instead of SELECT *.Table.insert(select_query) with no columns raising TypeError instead of rendering INSERT INTO t SELECT ....ON DELETE/ON UPDATE action when add_not_null() or rename_column() rebuilds the constraint, silently downgrading e.g. CASCADE to RESTRICT. The actions reported by get_foreign_keys() are now carried through to the rebuilt constraint.postgres_ext JSON contains/contained_by/concat raising AttributeError, and remove() 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 sibling set/replace/insert/append/update mutators.postgres_ext.JSONField docs: the json-column field does not support the jsonb-based mutation/concatenation builders (they raise ProgrammingError), so the misleading "Postgres casts implicitly" claim was removed and new code is steered to the built-in JSONField.UNIQUE (a, b) constraint as a column when rebuilding a table (add_not_null, drop_column, ...), raising no column named UNIQUE; unique is now recognized as a constraint.CREATE TABLE keywords 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.Change Field.__hash__ again... fml. Use (model_cls, field name) .
Field.__hash__ again... fml. Use (model_cls, field name).Metadata.remove_ref() removing the wrong foreign-key when a model has multiple foreign-keys to the same target, as list.remove() matched the first entry via the overloaded Field.__eq__.Case or Cast collapsing to its alias in an UPDATE ... SET value and in ON CONFLICT DO UPDATE, as qualify_names() wrapped the value at SCOPE_COLUMN.namedtuples() on a query-builder (Table) query raising ValueError when a column name is not a valid identifier. The plain NamedTupleCursorWrapper now passes rename=True, matching the model path.None, so accessing the attribute raised AttributeError. The outer-join test had regressed to endswith('OUTER') (never true). It now also recognizes FULL JOIN and LEFT JOIN LATERAL.ModelSelect.select_extend() mutating its receiver's default-projection flag, so a base Model.select() reused as a subquery stopped collapsing to its primary key. It now flags the returned clone, matching select().distinct(True) and distinct(False) not clearing a prior distinct(*columns), so the query kept rendering DISTINCT ON (...) instead of a plain DISTINCT or no distinct at all.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.Model.insert(), insert({})) emitting DEFAULT VALUES and dropping python-side field defaults, inconsistent with a partial insert which backfills them. A model with no python defaults still uses DEFAULT VALUES.Can't ship a stub that's not complete. Missed moving server_side_cursor() helper into the core psycopg helper.
Add django-style filter lookups: contains , startswith , endswith , between , is_null , not_in and iregexp .
contains, startswith, endswith, between, is_null, not_in and iregexp.PostgresqlDatabase(isolation_level=...) having no effect on transactions. Previously only atomic(isolation_level=...) worked.Ordering.collate() dropping the nulls= ordering.get_indexes().windows= parameter of the Select constructor.reraise(), __div__, __nonzero__) and assorted dead internal code.TimestampField.local_to_utc() and TimestampField.utc_to_local().Select.columns() no longer accepts and ignores keyword arguments.Metadata.get_rel_for_model().SelectBase.exists() ignoring its database argument.CursorWrapper indexing: cursor[n] raised IndexError for uncached rows and cursor[0] fetched the entire result set..namedtuples() crashing on selected columns that are not valid Python identifiers.materialized= when compounding CTEs via union()/union_all().ManyToManyField reads when the through-model foreign keys use the '!' backref sentinel.mariadb connector - pooled connections were discarded on every checkout.sqliteq stop() to drain the write queue and return True.NameErrors in cysqlite_ext: blob_open() and progress().attr= keyword instead of on_delete/on_update for reflected foreign keys.dataset infinite 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.model_to_dict to honor only=/exclude= for many-to-many fields, fix resolve_multimodel_query on queries with narrowed selections.signals.Model.save(True) reporting created=False when force_insert is passed positionally.CompressedField crashing on str values.withhold) and CockroachDB run_transaction retry detection under psycopg3.peewee logger.playhouse; remove the broken, unused get_current_url/get_next_url helpers from flask_utils.delete_instance(recursive=True) failing to cascade to the children of a model reachable through both nullable and non-nullable foreign-keys.fn.SUM(Case(...)).Table inserts on returning-clause databases binding the primary-key name as a parameter and returning None instead of the new id.CompositeKey comparisons raise ValueError when the value's length does not match the key, rather than silently matching on a prefix.FieldAlias.model to 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.playhouse.postgres_ext.JSONField creating jsonb columns after the core postgres backend began mapping the JSON field-type to JSONB; its DDL is json again, and json-vs-jsonb function selection for chained lookups now follows the field's declared datatype.COUNT rather than COUNT(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.UnboundLocalError when 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, CompressedField and the sqlite_udf.gzip() function encode str values using utf-8 instead of raw_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.
Ensure quotes escaped in SQLite introspection methods, thanks @greymoth-jp for reporting and the initial patch.
has_key, has_keys, has_any_keys) to the core JSONField on SQLite, implemented with json_type().contains, contained_by) to the core JSONField on SQLite via a registered _pw_json_contains UDF that emulates Postgres' @> semantics (structural, level-aligned). The core JSONField now has full predicate parity across SQLite, Postgres, and MySQL/MariaDB.Good ol' blimby!
New declarative API for pre-fetching related instances (Load()). See documentation. This replaces prefetch(), is more flexible and also supports optio
Load()). See
documentation.
This replaces prefetch(), is more flexible and also supports options for
applying a row limit to sub-results, and a strategy that materializes the ID
list (in addition to SELECT IN and JOIN strategies).MySQLJSONField (playhouse.mysql_ext) with contains_any() for the
JSON_OVERLAPS/"match any" counterpart to contains for JSON arrays.lazy_load=False when serializing
recursively with model_to_dict(), #3055.Unfortunately, the new JSONField did not play nice w/MySQL when query was generated *before* a conn was opened. We were trying to do some introspectio
mariadb= be a
database param, per @alisonatwork's suggestion, with the default being
"MySQL" flavored JSON. Refs #3053JSONField containment (contains, contained_by) no longer wraps its
argument in CAST / JSON_COMPACT on MySQL/MariaDB, #3053.Ensure new JSONField can be inherited, #3052
Add BaseQuery.aexecute() - an async twin of execute() available on all query types, executing through the query's bound async database: await User.sel
BaseQuery.aexecute() - an async twin of execute() available on all
query types, executing through the query's bound async database:
await User.select().aexecute(), await user.tweets.aexecute(). Returns
exactly what execute() returns, including result rows for DML with
RETURNING. Queries remain non-awaitable, this is an ordinary coroutine
method and the only async method on queries.playhouse.pwasyncio using "a"-prefixed coroutine
counterparts of the row-level Model methods (acreate, aget,
aget_or_none, aget_by_id, aget_or_create, aset_by_id,
adelete_by_id, abulk_create, abulk_update, asave,
adelete_instance), available via the new AsyncModel /
AsyncModelMixin classes. Each is a thin delegation through the greenlet
bridge, so behavior is identical to the synchronous implementation.
Note: the Model property of async databases now returns a base class
that includes these methods - relevant only if you introspect the base
class of db.Model subclasses.afetch() for explicit, awaitable lazy foreign-key resolution:
user = await tweet.afetch(Tweet.user). Already-loaded relations (via
join or prefetch) return immediately without a query.db.first(query, n=1) async helper.MissingGreenletBridge errors now include a hint describing the async
APIs to use.Eliminate use of deprecated params when connecting to MySQL databases, thanks to @abulgher, #3050.
playhouse.pwasyncio: report correct UPDATE / DELETE rowcounts on
asyncpg, roll back open transactions when connections are returned to the
pool, raise instead of deadlocking when querying during iterate(), and
detect the MySQL / MariaDB server version.playhouse.pwasyncio fixes: a second iterate() on a busy
connection raises instead of deadlocking, asyncpg exceptions are translated
to peewee exception types, registered aggregates / collations / window
functions / extensions and timeout are applied to async SQLite
connections, :memory: databases use a single connection, atomic()
accepts transaction arguments (e.g. lock_type), postgres connection URLs
and isolation_level are supported, %% in raw SQL is unescaped, and
attempting a query outside the greenlet bridge no longer emits "never
awaited" warnings.playhouse.pydantic_utils: JSON fields validate as Any (now
including the sqlite_ext JSONField), foreign keys may be included /
excluded by field name or column name, server-side defaults like
SQL('CURRENT_TIMESTAMP') are no longer emitted as schema defaults, and
relationships keys are validated.JSONField to core that provides basic operations
and also more consistent behavior when reading data. By default the new core
JSONField treats extracted values as JSON, which is generally the correct
thing, but "text-mode" is available as a chained .as_text() method. See
docs.
May eventually replace the backend-specific implementations with subclasses
that inherit semantics of this new field.
Note: playhouse.mysql_ext.JSONField is now the core field. The old
json_dumps / json_loads arguments are renamed dumps / loads, the
extract() method is removed (use item-access or path()), and MySQL
tables are now created with JSON columns rather than TEXT.fromisoformat() ended up causing previously-unconverted strings (Ymd)
to be converted in some cases, e.g. formatting a datetime as a str (#3051).
The change I made to address this is to make explicit casts on function calls
not attempt any heuristic python-value conversion. This makes it more natural
to call fn.whatever().cast('text') and you predictably get text out.Add new methods to the postgres BinaryJSONField: helpers for in-place modifications (set, replace, insert, append, update).
BinaryJSONField: helpers for in-place
modifications (set, replace, insert, append, update).BinaryJSONField (path_exists,
path_match, path_query, path_query_array, path_query_first).fromisoformat as a first attempt since it's faster and more robust.db.connection_context() can be nested cleanly, #3046.pool.close_all and pool.manual_close, #3047.FixedCharField, #3048.Fix bug where db_value() may not get called in subclasses of Postgres JSONField / BinaryJSONField, refs #3044.
db_value() may not get called in subclasses of Postgres
JSONField / BinaryJSONField, refs #3044.Fix SQL generation for partial indexes with nulls (not) distinct clause.
ImproperlyConfigured if pg driver unavailable at model
definition-time when field db-hooks are used, rather than AttributeError.Refactor test suite - this was a mechanical refactor, just moving things around and trying to group things more clearly. Also added new tests covering
.in(a for a in iterable if cond).FOR UPDATE clauses.paginate() method.Remove all Python 2.x compatibility code.
db.iterate(query).base64_bytes=True.BinaryJSONField, support atomic removal of
sub-elements, as well as alternate helper for extracting sub-elements and
querying array length.Ensure gr_context is set on greenlet in greenlet_spawn so that contextvars will be operable in sync handlers.
gr_context is set on greenlet in greenlet_spawn so that
contextvars will be operable in sync handlers.SqliteExtDatabase (it basically served no purpose in 4.0). Use
SqliteDatabase instead.playhouse.pool.dumps option for postgres JSON fields.Adds preliminary support for asyncio via a new playhouse extension. See the documentation for details.
asyncio via a new playhouse extension. See
the documentation
for details.PostgresqlDatabase can use psycopg (psycopg3) if it is installed. If both
psycopg2 and psycopg3 are installed, Peewee will prefer psycopg2, but this
can be controlled by specifying prefer_psycopg3=True in the constructor.
Same applies to PostgresqlExtDatabase.Psycopg3Database class has been moved to playhouse.postgres_ext and is
now just a thin wrapper around PostgresqlExtDatabase.Json() wrapper(s).ISODateTimeField for Sqlite that encodes datetimes in ISO format
(more friendly when db is shared with other tools), and also properly reads
back UTC offset info.playhouse.sqlite_ext.ClosureTable implementation.Model.dirty_field_names attribute that is safe for membership
testing, since testing x in dirty_fields returns True if one or more field
exists due to operator overloads returning a truthy Expression object.
Refs #3028._sqlite_ext extension. The C implementations of the FTS
rank functions are moved to sqlite_udf. Most of the remaining functionality
is moved to playhouse.cysqlite_ext which supports it natively.Migrating CSqliteExtDatabase usage:
You can either use sqlite_ext.SqliteExtDatabase or try the new
cysqlite_ext.CySqliteDatabase if you want all the old functionality and are
willing to try a new driver.
Move to new build system using pyproject and github actions.
pip install peewee --no-binary :all:.Rationale about the Sqlite C extensions -- I've started shipping pysqlite3 as a statically-linked, self-contained binary wheel. This means that when using Peewee with the statically-linked pysqlite3, you can end up in a funny situation where the peewee Sqlite extensions are linked against the system libsqlite3, and the pysqlite driver has it's own Sqlite embedded, which does not work.
If you are using the system/standard-lib sqlite3 module then the extension
works properly, because everything is talking to the same libsqlite3.
Similarly if you built pysqlite3 to link against the system libsqlite3
everything also works correctly, though this is not "wheel-friendly".
So in order to use the C extensions, you can install Peewee from the sdist and do either of the following:
# Use system sqlite and standard-lib `sqlite3` module.
$ pip install peewee --no-binary :all:
# OR,
# Use pysqlite3 linked against the system sqlite.
$ pip install pysqlite3 peewee --no-binary :all:
I don't believe, besides myself, there were many people using these extensions so hopefully this change is not disruptive! Please let me hear about it if I'm mistaken.
Other small changes:
playhouse.dataset JSON
serializer, encode binary data as base64.Fix potential regex DoS vulnerability in FTS5 query validation code (#3005).
Cython 3.1 removes some Python 2 stuff we referenced -- this resolves the issue. Couple other very minor fixes.
Cython 3.1 removes some Python 2 stuff we referenced -- this resolves the issue. Couple other very minor fixes.
@pypa is such a bunch of clowns. I swear.
@pypa is such a bunch of clowns. I swear.
The behavior of postgresql_ext.BinaryJSONField.contains() has changed. Previously, passing a string to this method would perform a JSON key exists che
The behavior of postgresql_ext.BinaryJSONField.contains() has changed.
Previously, passing a string to this method would perform a JSON key exists
check (? operator) instead of JSON contains (@> operator). As of 3.18.0,
this special-case has been removed and the contains() method always uses
the JSONB contains operator (@>). For the old behavior of checking
whether a key exists, use the BinaryJSONField.has_key() method. See #2984 for
discussion.
db_url helpers,
see #2974 for discussion.postgresql:// URLs when connecting to psycopg3.Fix incorrect handling of fk constraint name in migrator.
Fix regression in behavior of delete_instance() when traversing nullable foreign-keys, #2952. Introduced in 3.17.6. Recommended that you update.
delete_instance() when traversing nullable
foreign-keys, #2952. Introduced in 3.17.6. Recommended that you update.Add db_url support for psycopg3 via psycopg3://.
psycopg3://.Fix bug in recursive model.delete_instance() when a table contains foreign-keys at multiple depths of the graph, #2893.
model.delete_instance() when a table contains
foreign-keys at multiple depths of the graph, #2893.time.time() returns
identical values for two connections. This adds a no-op comparable sentinel
to the heap to prevent any recurrence of this problem, #2901.CASE statements generate correct SQL.Connection instance.This release fixes a build system problem in Python 3.12, #2891.
This release fixes a build system problem in Python 3.12, #2891.
Fix bug that could occur when using CASE inside a function, and one or more of the CASE clauses consisted of a subquery. Refs #2873. new fix in #2872
Better fix for #2871 (extraneous queries when coercing query to list), and
Basic support for Sqlite jsonb.
psycopg3.jsonb.list(query) resulted in extra queries, #2871Fix compatibility issues with 3.12 related to utcnow() deprecation.
BigBitField, #2802.add_column_default and drop_column_default migrator methods for
specifying a server-side default value, #2803.star attribute was causing issues for users who had a field named
star on their models. This attribute is now renamed to __star__. #2796.model_to_dict() when only aliases are present.reconnect= argument to ping() if using MySQL 8.x.Only roll-back in the outermost @db.transaction decorator/ctx manager if an unhandled exception occurs. Previously, an unhandled exception that occurr
@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.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.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.SQLITE_DETERMINISTIC function flag. This allows
user-defined Sqlite functions to be used in indexes and may be used by the
query planner.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
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.star() helper to Source base-class for selecting all columns.binary types for mysql-connector and mariadb-connector.extract() method to MySQL JSONField for extracting a jsonpath.Fixes a longstanding issue with thread-safety of various decorators, including atomic(), transaction(), savepoint(). The context-managers are unaffect
Fixes a longstanding issue with thread-safety of various decorators, including
atomic(), transaction(), savepoint(). The context-managers are
unaffected. See #2709 for details.
Add changes required for building against Cython 3.0 and set Cython language-level to 3.
legacy=True flag.The commit= argument is deprecated for the cursor(), execute() and execute_sql() methods.
This release contains backwards-incompatible changes in the way Peewee initializes connections to the underlying database driver. Previously, peewee implemented autocommit semantics on-top of the existing DB-API transactional workflow. Going forward, Peewee instead places the DB-API driver into autocommit mode directly.
Why this change?
Previously, Peewee emulated autocommit behavior for top-level queries issued outside of a transaction. This necessitated a number of checks which had to be performed each time a query was executed, so as to ensure that we didn't end up with uncommitted writes or, conversely, idle read transactions. By running the underlying driver in autocommit mode, we can eliminate all these checks, since we are already managing transactions ourselves.
Behaviorally, there should be no change -- Peewee will still treat top-level
queries outside of transactions as being autocommitted, while queries inside of
atomic() / with db: blocks are implicitly committed at the end of the
block, or rolled-back if an exception occurs.
How might this affect me?
Database.connection() or Database.cursor(), your queries will now be
executed in autocommit mode.commit= argument is deprecated for the cursor(), execute() and
execute_sql() methods.Database implementation (whether for a database that
is not officially supported, or for the purpose of overriding default
behaviors), you will want to ensure that your connections are opened in
autocommit mode.Other changes:
get_columns() implementation now returns columns in their declared
order.Raise an exception in ReconnectMixin if connection is lost while inside a transaction (if the transaction was interrupted presumably some changes were
ReconnectMixin if connection is lost while inside a
transaction (if the transaction was interrupted presumably some changes were
lost and explicit intervention is needed).db.Model property to reduce boilerplate.prefetch() queries with joins instead of subqueries
(this helps overcome a MySQL limitation about applying LIMITs to a subquery).AVG to whitelist to avoid coercing by default.pyproject.toml to silence warnings from newer pips when wheel
package is not available.This release has a small helper for reducing boilerplate in some cases by exposing a base model class as an attribute of the database instance.
# old:
db = SqliteDatabase('...')
class BaseModel(Model):
class Meta:
database = db
class MyModel(BaseModel):
pass
# new:
db = SqliteDatabase('...')
class MyModel(db.Model):
pass
Add scalars() query method (complements scalar()), roughly equivalent to writing [t[0] for t in query.tuples()].
scalars() query method (complements scalar()), roughly equivalent to
writing [t[0] for t in query.tuples()].Fix bug where field-specific conversions were being applied to the pattern used for LIKE / ILIKE operations. Refs #2609
__iter__ method
on certain Column subclasses. Refs #2606Fix issue introduced in Sqlite 3.39.0 regarding the propagation of column subtypes in subqueries.
Rollback behavior change in commit ab43376697 (GH #2026). Peewee will no longer automatically return the cursor rowcount for certain bulk-inserts. Thi
Rollback behavior change in commit ab43376697 (GH #2026). Peewee will no longer
automatically return the cursor rowcount for certain bulk-inserts. This
should mainly affect users of MySQL and Sqlite who relied on a bulk INSERT
returning the rowcount (as opposed to the cursor's lastrowid). The
rowcount behavior is still available chaining the as_rowcount() method:
# NOTE: this change only affects MySQL or Sqlite.
db = MySQLDatabase(...)
# Previously, bulk inserts of the following forms would return the rowcount.
query = User.insert_many(...) # Bulk insert.
query = User.insert_from(...) # Bulk insert (INSERT INTO .. SELECT FROM).
# Previous behavior (peewee 3.12 - 3.14.10):
# rows_inserted = query.execute()
# New behavior:
last_id = query.execute()
# To get the old behavior back:
rows_inserted = query.as_rowcount().execute()
Additionally, in previous versions specifying an empty .returning() with
Postgres would cause the rowcount to be returned. For Postgres users who wish
to receive the rowcount:
# NOTE: this change only affects Postgresql.
db = PostgresqlDatabase(...)
# Previously, an empty returning() would return the rowcount.
query = User.insert_many(...) # Bulk insert.
query = User.insert_from(...) # Bulk insert (INSERT INTO .. SELECT FROM).
# Old behavior:
# rows_inserted = query.returning().execute()
# To get the rows inserted in 3.15 and newer:
rows_inserted = query.as_rowcount().execute()
This release contains a fix for a long-standing request to allow data-modifying queries to support CTEs. CTEs are now supported for use with INSERT, DELETE and UPDATE queries - see #2152.
Additionally, this release adds better support for using the new RETURNING
syntax with Sqlite automatically. Specify returning_clause=True when
initializing your SqliteDatabase and all bulk inserts will automatically
specify a RETURNING clause, returning the newly-inserted primary keys. This
functionality requires Sqlite 3.35 or newer.
Smaller changes:
shortcuts.insert_where() helper for generating conditional INSERT with
a bit less boilerplate.test_utils.count_queres() which could erroneously include pool
events such as connect/disconnect, etc.Add shortcut for conditional insert using sub-select, see #2528
left_outer_join() method to query.selected_columns property to Select queries.name property to Alias instances.Allow calling table_exists() with a model-class, refs
table_exists() with a model-class, refsis_connection_usable() method of MySQLDatabase class.playhouse.dataset.DataSet and sqlite-web.playhosue.kv for newer Sqlite.ArrayField.contained_by() method, a corollary to contains() and
the contains_any() methods.setup.py in the event distutils is not available.Back-out all changes to automatically use RETURNING for SqliteExtDatabase, CSqliteExtDatabase and APSWDatabase. The issue I found is that when a RETUR
Back-out all changes to automatically use RETURNING for SqliteExtDatabase,
CSqliteExtDatabase and APSWDatabase. The issue I found is that when a
RETURNING cursor is not fully-consumed, any parent SAVEPOINT (and possibly
transaction) would not be able to be released. Since this is a
backwards-incompatible change, I am going to back it out for now.
Returning clause can still be specified for Sqlite, however it just needs to be done so manually rather than having it applied automatically.
Fix bug in APSW extension with Sqlite 3.35 and newer, due to handling of last insert rowid with RETURNING. Refs #2479.
Fix bug in APSW extension with Sqlite 3.35 and newer, due to handling of last insert rowid with RETURNING. Refs #2479.
Fix pesky bug in new last_insert_id() on the SqliteExtDatabase.
Fix pesky bug in new last_insert_id() on the SqliteExtDatabase.
This release contains an important fix for a regression introduced by commit ebe3ad5, which affected the way model instances are converted to paramete
This release contains an important fix for a regression introduced by commit ebe3ad5, which affected the way model instances are converted to parameters for use in expressions within a query. The bug could manifest when code uses model instances as parameters in expressions against fields that are not foreign-keys.
The issue is described in #2376.
This release contains a single fix for ensuring NULL values are inserted when issuing a bulk-insert of heterogeneous dictionaries which may be missing
This release contains a single fix for ensuring NULL values are inserted when issuing a bulk-insert of heterogeneous dictionaries which may be missing explicit NULL values. Fixes issue #2638.
This is a small release mainly to get some fixes out.
This is a small release mainly to get some fixes out.
Check and foreign-key constraints.fn.array_agg() to blacklist for automatic value coercion.This release contains primarily bugfixes.
This release contains primarily bugfixes.
db_value() function when
converting model instances. #2304.DataSet.create_index() method, #2319.CHECK() constraints in Sqlite migrator, #2343.stddev aggregate function to collection of sqlite user-defined funcs.This release has been a bit overdue and there are numerous small improvements and bug-fixes. The bugfix that prompted this release is #2293, which is
This release has been a bit overdue and there are numerous small improvements
and bug-fixes. The bugfix that prompted this release is #2293, which is a
regression in the Django-inspired .filter() APIs that could cause some
filter expressions to be discarded from the generated SQL. Many thanks for the
excellent bug report, Jakub.
shortcuts.resolve_multimodel_query(), for
resolving multiple models used in a compound select query.lateral() method to select query for use with lateral joins, refs
issue #2205.converter() to be used for specifying a function to use to
convert a row-value pulled off the cursor, refs #2248.set() and clear() method to the bitfield flag descriptor, refs #2257.range types with IN and other expressions.save() and only=, refs #2269.filter() where using both *args and **kwargs caused
the expressions passed as args to be discarded. See #2293.Allow arbitrary keyword arguments to be passed to DataSet constructor, which are then passed to the instrospector.
DataSet constructor,
which are then passed to the instrospector.bulk_create() when model being inserted uses FK identifiers.bulk_update() so that PK values are properly coerced to the right
data-type (e.g. UUIDs to strings for Sqlite).Your coding agent can read these notes before it upgrades. Set up the MCP server →