2.1 Changelog

2.1.0b1

no release date

platform

  • [platform] [feature]

    Free-threaded Python versions are now supported in wheels released on Pypi. This integrates with overall free-threaded support added as part of #12881 for the 2.0 and 2.1 series, which includes new test suites as well as a few improvements to race conditions observed under freethreading.

    References: #12881

  • [platform] [change]

    Updated the setup manifest definition to use PEP 621-compliant pyproject.toml. Also updated the extra install dependency to comply with PEP-685. Thanks for the help of Matt Oberle and KOLANICH on this change.

  • [platform] [change]

    The greenlet dependency used for asyncio support no longer installs by default. This dependency does not publish wheel files for every architecture and is not needed for applications that aren’t using asyncio features. Use the sqlalchemy[asyncio] install target to include this dependency.

    References: #10197

  • [platform] [change]

    Python 3.10 or above is now required; support for Python 3.9, 3.8 and 3.7 is dropped as these versions are EOL.

    References: #10357, #12029, #12819

orm

  • [orm] [feature]

    Added new hybrid method hybrid_property.bulk_dml() which works in a similar way as hybrid_property.update_expression() for bulk ORM operations. A user-defined class method can now populate a bulk insert mapping dictionary using the desired hybrid mechanics. New documentation is added showing how both of these methods can be used including in combination with the new from_dml_column() construct.

    References: #12496

  • [orm] [feature]

    Added RegistryEvents event class that allows event listeners to be established on a registry object. The new class provides three events: RegistryEvents.resolve_type_annotation() which allows customization of type annotation resolution that can supplement or replace the use of the registry.type_annotation_map dictionary, including that it can be helpful with custom resolution for complex types such as those of PEP 695, as well as RegistryEvents.before_configured() and RegistryEvents.after_configured(), which are registry-local forms of the mapper-wide version of these hooks.

    References: #9832

  • [orm] [feature]

    The relationship.back_populates argument to relationship() may now be passed as a Python callable, which resolves to either the direct linked ORM attribute, or a string value as before. ORM attributes are also accepted directly by relationship.back_populates. This change allows type checkers and IDEs to confirm the argument for relationship.back_populates is valid. Thanks to Priyanshu Parikh for the help on suggesting and helping to implement this feature.

    References: #10050

  • [orm] [feature]

    Added support for per-session execution options that are merged into all queries executed within that session. The Session, sessionmaker, scoped_session, _ext.asyncio.AsyncSession, and _ext.asyncio.async_sessionmaker constructors now accept an Session.execution_options parameter that will be applied to all explicit query executions (e.g. using Session.execute(), Session.get(), Session.scalars()) for that session instance.

    References: #12659

  • [orm] [feature]

    Added new parameter composite.return_none_on to composite(), which allows control over if and when this composite attribute should resolve to None when queried or retrieved from the object directly. By default, a composite object is always present on the attribute, including for a pending object which is a behavioral change since 2.0. When composite.return_none_on is specified, a callable is passed that returns True or False to indicate if the given arguments indicate the composite should be returned as None. This parameter may also be set automatically when ORM Annotated Declarative is used; if the annotation is given as Mapped[SomeClass|None], a composite.return_none_on rule is applied that will return None if all contained columns are themselves None.

    References: #12570

  • [orm] [feature]

    Session autoflush behavior has been simplified to unconditionally flush the session each time an execution takes place, regardless of whether an ORM statement or Core statement is being executed. This change eliminates the previous conditional logic that only flushed when ORM-related statements were detected, which had become difficult to define clearly with the unified v2 syntax that allows both Core and ORM execution patterns. The change provides more consistent and predictable session behavior across all types of SQL execution.

    References: #9809

  • [orm] [usecase]

    Improvements to the use case of using Declarative Dataclass Mapping with intermediary classes that are unmapped. As was the existing behavior, classes can subclass MappedAsDataclass alone without a declarative base to act as mixins, or along with a declarative base as well as __abstract__ = True to define an abstract base. However, the improved behavior scans ORM attributes like mapped_column() in this case to create correct dataclasses.field() constructs based on their arguments, allowing for more natural ordering of fields without dataclass errors being thrown. Additionally, added a new unmapped_dataclass() decorator function, which may be used to create unmapped mixins in a mapped hierarchy that is using the mapped_dataclass() decorator to create mapped dataclasses.

    References: #12854

  • [orm] [usecase]

    The aliased() object now emits warnings when an attribute is accessed on an aliased class that cannot be located in the target selectable, for those cases where the aliased() is against a different FROM clause than the regular mapped table (such as a subquery). This helps users identify cases where column names don’t match between the aliased class and the underlying selectable. When aliased.adapt_on_names is True, the warning suggests checking the column name; when False, it suggests using the adapt_on_names parameter for name-based matching.

    References: #12838

  • [orm] [usecase]

    The Session.flush.objects parameter is now deprecated.

    References: #10816

  • [orm] [usecase]

    Added the utility method Session.merge_all() and Session.delete_all() that operate on a collection of instances.

    References: #11776

  • [orm] [usecase]

    Added default implementations of ColumnOperators.desc(), ColumnOperators.asc(), ColumnOperators.nulls_first(), ColumnOperators.nulls_last() to composite() attributes, by default applying the modifier to all contained columns. Can be overridden using a custom comparator.

    References: #12769

  • [orm] [change]

    A sweep through class and function names in the ORM renames many classes and functions that have no intent of public visibility to be underscored. This is to reduce ambiguity as to which APIs are intended to be targeted by third party applications and extensions. Third parties are encouraged to propose new public APIs in Discussions to the extent they are needed to replace those that have been clarified as private.

    References: #10497

  • [orm] [change]

    Removed legacy signatures dating back to 0.9 release from the SessionEvents.after_bulk_update() and SessionEvents.after_bulk_delete().

    References: #10721

  • [orm] [change]

    The first_init ORM event has been removed. This event was non-functional throughout the 1.4 and 2.0 series and could not be invoked without raising an internal error, so it is not expected that there is any real-world use of this event hook.

    References: #10500

  • [orm] [changed]

    The "non primary" mapper feature, long deprecated in SQLAlchemy since version 1.3, has been removed. The sole use case for "non primary" mappers was that of using relationship() to link to a mapped class against an alternative selectable; this use case is now suited by the Relationship to Aliased Class feature.

    References: #12437

  • [orm] [bug]

    Fixed issue where joined eager loading would fail to use the "nested" form of the query when GROUP BY or DISTINCT were present if the eager joins being added were many-to-ones, leading to additional columns in the columns clause which would then cause errors. The check for "nested" is tuned to be enabled for these queries even for many-to-one joined eager loaders, and the "only do nested if it’s one to many" aspect is now localized to when the query only has LIMIT or OFFSET added.

    References: #11226

  • [orm] [bug]

    A significant behavioral change has been made to the behavior of the mapped_column.default and relationship.default parameters, as well as the relationship.default_factory parameter with collection-based relationships, when used with SQLAlchemy’s Declarative Dataclass Mapping feature introduced in 2.0, where the given value (assumed to be an immutable scalar value for mapped_column.default and a simple collection class for relationship.default_factory) is no longer passed to the @dataclass API as a real default, instead a token that leaves the value un-set in the object’s __dict__ is used, in conjunction with a descriptor-level default. This prevents an un-set default value from overriding a default that was actually set elsewhere, such as in relationship / foreign key assignment patterns as well as in Session.merge() scenarios. See the full writeup in the What’s New in SQLAlchemy 2.1? document which includes guidance on how to re-enable the 2.0 version of the behavior if needed.

    References: #12168

  • [orm] [bug]

    The relationship.secondary parameter no longer uses Python eval() to evaluate the given string. This parameter when passed a string should resolve to a table name that’s present in the local MetaData collection only, and never needs to be any kind of Python expression otherwise. To use a real deferred callable based on a name that may not be locally present yet, use a lambda instead.

    References: #10564

  • [orm] [bug]

    Revised the set "binary" operators for the association proxy set() interface to correctly raise TypeError for invalid use of the |, &, ^, and - operators, as well as the in-place mutation versions of these methods, to match the behavior of standard Python set() as well as SQLAlchemy ORM’s "intstrumented" set implementation.

    References: #11349

  • [orm] [bug]

    Improved the behavior of standalone "operators" like desc(), asc(), all_(), etc. so that they consult the given expression object for an overriding method for that operator, even if the object is not itself a ClauseElement, such as if it’s an ORM attribute. This allows custom comparators for things like composite() to provide custom implementations of methods like desc(), asc(), etc.

    References: #12769

  • [orm] [bug]

    ORM entities can now be involved within the SQL expressions used within relationship.primaryjoin and relationship.secondaryjoin parameters without the ORM entity information being implicitly sanitized, allowing ORM-specific features such as single-inheritance criteria in subqueries to continue working even when used in this context. This is made possible by overall ORM simplifications that occurred as of the 2.0 series. The changes here also provide a performance boost (up to 20%) for certain query compilation scenarios.

    References: #12843

  • [orm] [bug]

    The behavior of with_polymorphic() when used with a single inheritance mapping has been changed such that its behavior should match as closely as possible to that of an equivalent joined inheritance mapping. Specifically this means that the base class specified in the with_polymorphic() construct will be the basemost class that is loaded, as well as all descendant classes of that basemost class. The change includes that the descendant classes named will no longer be exclusively indicated in "WHERE polymorphic_col IN" criteria; instead, the whole hierarchy starting with the given basemost class will be loaded. If the query indicates that rows should only be instances of a specific subclass within the polymorphic hierarchy, an error is raised if an incompatible superclass is loaded in the result since it cannot be made to match the requested class; this behavior is the same as what joined inheritance has done for many years. The change also allows a single result set to include column-level results from multiple sibling classes at once which was not previously possible with single table inheritance.

    References: #12395

  • [orm]

    Ignore Session.join_transaction_mode in all cases when the bind provided to the Session is an Engine. Previously if an event that executed before the session logic, like ConnectionEvents.engine_connect(), left the connection with an active transaction, the Session.join_transaction_mode behavior took place, leading to a surprising behavior.

    References: #11163

  • [orm]

    The noload() relationship loader option and related lazy='noload' setting is deprecated and will be removed in a future release. This option was originally intended for custom loader patterns that are no longer applicable in modern SQLAlchemy.

    References: #11045

engine

  • [engine] [usecase]

    Added new execution option Connection.execution_options.driver_column_names. This option disables the "name normalize" step that takes place against the DBAPI cursor.description for uppercase-default backends like Oracle, and will cause the keys of a result set (e.g. named tuple names, dictionary keys in Row._mapping, etc.) to be exactly what was delivered in cursor.description. This is mostly useful for plain textual statements using text() or Connection.exec_driver_sql().

    References: #10789

  • [engine] [change]

    An empty sequence passed to any execute() method now raised a deprecation warning, since such an executemany is invalid. Pull request courtesy of Carlos Sousa.

    References: #9647

  • [engine] [bug]

    Adjusted URL parsing and stringification to apply url quoting to the "database" portion of the URL. This allows a URL where the "database" portion includes special characters such as question marks to be accommodated.

    References: #11234

  • [engine] [bug]

    Fixed issue in "insertmanyvalues" feature where an INSERT..RETURNING that also made use of a sentinel column to track results would fail to filter out the additional column when Result.unique() were used to uniquify the result set.

    References: #10802

sql

  • [sql] [feature]

    Added the ability to create custom SQL constructs that can define new clauses within SELECT, INSERT, UPDATE, and DELETE statements without needing to modify the construction or compilation code of of Select, Insert, Update, or Delete directly. Support for testing these constructs, including caching support, is present along with an example test suite. The use case for these constructs is expected to be third party dialects for analytical SQL (so-called NewSQL) or other novel styles of database that introduce new clauses to these statements. A new example suite is included which illustrates the QUALIFY SQL construct used by several NewSQL databases which includes a cachable implementation as well as a test suite.

    References: #12195

  • [sql] [feature]

    Added new Core feature from_dml_column() that may be used in expressions inside of UpdateBase.values() for INSERT or UPDATE; this construct will copy whatever SQL expression is used for the given target column in the statement to be used with additional columns. The construct is mostly intended to be a helper with ORM hybrid_property within DML hooks.

    References: #12496

  • [sql] [feature] [core]

    The Core operator system now includes the matmul operator, i.e. the @ operator in Python as an optional operator. In addition to the __matmul__ and __rmatmul__ operator support this change also adds the missing __rrshift__ and __rlshift__. Pull request courtesy Aramís Segovia.

    References: #12479

  • [sql] [usecase]

    Added new generalized aggregate function ordering to functions via the aggregate_order_by() method, which receives an expression and generates the appropriate embedded "ORDER BY" or "WITHIN GROUP (ORDER BY)" phrase depending on backend database. This new function supersedes the use of the PostgreSQL aggregate_order_by() function, which remains present for backward compatibility. To complement the new parameter, the aggregate_strings.order_by which adds ORDER BY capability to the aggregate_strings dialect-agnostic function which works for all included backends. Thanks much to Reuven Starodubski with help on this patch.

    References: #12853

  • [sql] [usecase]

    Added support for the pow operator (**), with a default SQL implementation of the POW() function. On Oracle Database, PostgreSQL and MSSQL it renders as POWER(). As part of this change, the operator routes through a new first class func member pow, which renders on Oracle Database, PostgreSQL and MSSQL as POWER().

    References: #8579

  • [sql] [usecase]

    Added method TableClause.insert_column() to complement TableClause.append_column(), which inserts the given column at a specific index. This can be helpful for prepending primary key columns to tables, etc.

    References: #7910

  • [sql] [change]

    The .c and .columns attributes on the Select and TextualSelect constructs, which are not instances of FromClause, have been removed completely, in addition to the .select() method as well as other codepaths which would implicitly generate a subquery from a Select without the need to explicitly call the Select.subquery() method.

    In the case of .c and .columns, these attributes were never useful in practice and have caused a great deal of confusion, hence were deprecated back in version 1.4, and have emitted warnings since that version. Accessing the columns that are specific to a Select construct is done via the Select.selected_columns attribute, which was added in version 1.4 to suit the use case that users often expected .c to accomplish. In the larger sense, implicit production of subqueries works against SQLAlchemy’s modern practice of making SQL structure as explicit as possible.

    Note that this is not related to the usual FromClause.c and FromClause.columns attributes, common to objects such as Table and Subquery, which are unaffected by this change.

    References: #10236

  • [sql] [change]

    the Numeric and Float SQL types have been separated out so that Float no longer inherits from Numeric; instead, they both extend from a common mixin NumericCommon. This corrects for some architectural shortcomings where numeric and float types are typically separate, and establishes more consistency with Integer also being a distinct type. The change should not have any end-user implications except for code that may be using isinstance() to test for the Numeric datatype; third party dialects which rely upon specific implementation types for numeric and/or float may also require adjustment to maintain compatibility.

    References: #5252

  • [sql] [bug]

    Fixed issue in name normalization (e.g. "uppercase" backends like Oracle) where using a TextualSelect would not properly maintain as uppercase column names that were quoted as uppercase, even though the TextualSelect includes a Column that explicitly holds this uppercase name.

    References: #10788

  • [sql] [bug]

    Enhanced the caching structure of the over.rows and over.range so that different numerical values for the rows / range fields are cached on the same cache key, to the extent that the underlying SQL does not actually change (i.e. "unbounded", "current row", negative/positive status will still change the cache key). This prevents the use of many different numerical range/rows value for a query that is otherwise identical from filling up the SQL cache.

    Note that the semi-private compiler method _format_frame_clause() is removed by this fix, replaced with a new method visit_frame_clause(). Third party dialects which may have referred to this method will need to change the name and revise the approach to rendering the correct SQL for that dialect.

    References: #11515

  • [sql] [bug]

    Added a new concept of "operator classes" to the SQL operators supported by SQLAlchemy, represented within the enum OperatorClass. The purpose of this structure is to provide an extra layer of validation when a particular kind of SQL operation is used with a particular datatype, to catch early the use of an operator that does not have any relevance to the datatype in use; a simple example is an integer or numeric column used with a "string match" operator.

    References: #12736

  • [sql]

    Removed the automatic coercion of executable objects, such as Query, when passed into Session.execute(). This usage raised a deprecation warning since the 1.4 series.

    References: #12218

schema

  • [schema] [bug]

    The Float and Numeric types are no longer automatically considered as auto-incrementing columns when the Column.autoincrement parameter is left at its default of "auto" on a Column that is part of the primary key. When the parameter is set to True, a Numeric type will be accepted as an auto-incrementing datatype for primary key columns, but only if its scale is explicitly given as zero; otherwise, an error is raised. This is a change from 2.0 where all numeric types including floats were automatically considered as "autoincrement" for primary key columns.

    References: #11811

  • [schema]

    Deprecate Oracle only parameters Sequence.order, Identity.order and Identity.on_null. They should be configured using the dialect kwargs oracle_order and oracle_on_null.

    References: #10247

typing

  • [typing] [feature]

    The Row object now no longer makes use of an intermediary Tuple in order to represent its individual element types; instead, the individual element types are present directly, via new PEP 646 integration, now available in more recent versions of Mypy. Mypy 1.7 or greater is now required for statements, results and rows to be correctly typed. Pull request courtesy Yurii Karabas.

    References: #10635

  • [typing] [orm]

    Removed the deprecated mypy plugin. The plugin was non-functional with newer version of mypy and it’s no longer needed with modern SQLAlchemy declarative style.

    References: #12293

  • [typing]

    The default implementation of TypeEngine.python_type now returns object instead of NotImplementedError, since that’s the base for all types in Python3. The python_type of JSON no longer returns dict, but instead fallbacks to the generic implementation.

    References: #10646

  • [typing] [orm]

    Deprecated the declarative_mixin decorator since it was used only by the now removed mypy plugin.

    References: #12346

asyncio

  • [asyncio] [feature]

    The "emulated" exception hierarchies for the asyncio drivers such as asyncpg, aiomysql, aioodbc, etc. have been standardized on a common base EmulatedDBAPIException, which is now what’s available from the StatementException.orig attribute on a SQLAlchemy DBAPIError object. Within EmulatedDBAPIException and the subclasses in its hiearchy, the original driver-level exception is also now avaliable via the EmulatedDBAPIException.orig attribute, and is also available from DBAPIError directly using the DBAPIError.driver_exception attribute.

    References: #8047

  • [asyncio] [change]

    Added an initialize step to the import of sqlalchemy.ext.asyncio so that greenlet will be imported only when the asyncio extension is first imported. Alternatively, the greenlet library is still imported lazily on first use to support use case that don’t make direct use of the SQLAlchemy asyncio extension.

    References: #10296

  • [asyncio] [change]

    Adapted all asyncio dialects, including aiosqlite, aiomysql, asyncmy, psycopg, asyncpg to use the generic asyncio connection adapter first added in #6521 for the aioodbc DBAPI, allowing these dialects to take advantage of a common framework.

    References: #10415

  • [asyncio] [change]

    Removed the compatibility async_fallback mode for async dialects, since it’s no longer used by SQLAlchemy tests. Also removed the internal function await_fallback() and renamed the internal function await_only() to await_(). No change is expected to user code.

  • [asyncio] [bug]

    Refactored all asyncio dialects so that exceptions which occur on failed connection attempts are appropriately wrapped with SQLAlchemy exception objects, allowing for consistent error handling.

    References: #11956

postgresql

  • [postgresql] [feature]

    Adds a new str subclass BitString representing PostgreSQL bitstrings in python, that includes functionality for converting to and from int and bytes, in addition to implementing utility methods and operators for dealing with bits.

    This new class is returned automatically by the postgresql.BIT type.

    References: #10556

  • [postgresql] [feature]

    Support for VIRTUAL computed columns on PostgreSQL 18 and later has been added. The default behavior when Computed.persisted is not specified has been changed to align with PostgreSQL 18’s default of VIRTUAL. When Computed.persisted is not specified, no keyword is rendered on PostgreSQL 18 and later; on older versions a warning is emitted and STORED is used as the default. To explicitly request STORED behavior on all PostgreSQL versions, specify persisted=True.

    References: #12866

  • [postgresql] [feature]

    Support for storage parameters in CREATE TABLE using the WITH clause has been added. The postgresql_with dialect option of Table accepts a mapping of key/value options.

    See also

    WITH - in the PostgreSQL dialect documentation

    References: #10909

  • [postgresql] [feature]

    Added additional emulated error classes for the subclasses of asyncpg.exception.IntegrityError including RestrictViolationError, NotNullViolationError, ForeignKeyViolationError, UniqueViolationError CheckViolationError, ExclusionViolationError. These exceptions are not directly thrown by SQLAlchemy’s asyncio emulation, however are available from the newly added DBAPIError.driver_exception attribute when a IntegrityError is caught.

    References: #8047

  • [postgresql] [feature]

    Added syntax extension distinct_on() to build DISTINCT ON clauses. The old api, that passed columns to Select.distinct(), is now deprecated.

    References: #12342

  • [postgresql] [usecase]

    The PostgreSQL dialect now support reflection of table options, including the storage parameters, table access method and table spaces. These options are automatically reflected when autoloading a table, and are also available via the Inspector.get_table_options() and Inspector.get_multi_table_optionsmethod() methods.

    References: #10909

  • [postgresql] [usecase]

    Added new parameter Enum.create_type to the Core Enum class. This parameter is automatically passed to the corresponding ENUM native type during DDL operations, allowing control over whether the PostgreSQL ENUM type is implicitly created or dropped within DDL operations that are otherwise targeting tables only. This provides control over the ENUM.create_type behavior without requiring explicit creation of a ENUM object.

    References: #10604

  • [postgresql] [change]

    The Comparator.any() and Comparator.all() methods for the ARRAY type are now deprecated for removal; these two methods along with Any() and All() have been legacy for some time as they are superseded by the any_() and all_() functions, which feature more intutive use.

    References: #10821

  • [postgresql] [change]

    Named types such as ENUM and DOMAIN (as well as the dialect-agnostic Enum version) are now more strongly associated with the MetaData at the top of the table hierarchy and are de-associated with any particular Table they may be a part of. This better represents how PostgreSQL named types exist independently of any particular table, and that they may be used across many tables simultaneously. The change impacts the behavior of the "default schema" for a named type, as well as the CREATE/DROP behavior in relationship to the MetaData and Table construct. The change also includes a new CheckFirst enumeration which allows fine grained control over "check" queries during DDL operations, as well as that the SchemaType.inherit_schema parameter is deprecated and will emit a deprecation warning when used. See the migration notes for full details.

    See also

    Changes to Named Type Handling in PostgreSQL - Complete details on PostgreSQL named type changes

    References: #10594, #12690

  • [postgresql] [bug]

    A CompileError is raised if attempting to create a PostgreSQL ENUM or DOMAIN datatype using a name that matches a known pg_catalog datatype name, and a default schema is not specified. These types must be explicit within a schema in order to be differentiated from the built-in pg_catalog type. The "public" or otherwise default schema is not chosen by default here since the type can only be reflected back using the explicit schema name as well (it is otherwise not visible due to the pg_catalog name). Pull request courtesy Kapil Dagur.

    References: #12761

mysql

  • [mysql] [feature]

    Added new construct limit() which can be applied to any update() or delete() to provide the LIMIT keyword to UPDATE and DELETE. This new construct supersedes the use of the "mysql_limit" dialect keyword argument.

  • [mysql] [mariadb] [reflection]

    Updated the reflection logic for indexes in the MariaDB and MySQL dialect to avoid setting the undocumented type key in the ReflectedIndex dicts returned by get_indexes method.

    References: #12240

mariadb

  • [mariadb] [usecase]

    Modified the MariaDB dialect so that when using the Uuid datatype with MariaDB >= 10.7, leaving the Uuid.native_uuid parameter at its default of True, the native UUID datatype will be rendered in DDL and used for database communication, rather than CHAR(32) (the non-native UUID type) as was the case previously. This is a behavioral change since 2.0, where the generic Uuid datatype delivered CHAR(32) for all MySQL and MariaDB variants. Support for all major DBAPIs is implemented including support for less common "insertmanyvalues" scenarios where UUID values are generated in different ways for primary keys. Thanks much to Volodymyr Kochetkov for delivering the PR.

    References: #10339

sqlite

mssql

  • [mssql] [bug]

    The Comparator.as_boolean() method when used on a JSON value on SQL Server will now force a cast to occur for values that are not simple true/false JSON literals, forcing SQL Server to attempt to interpret the given value as a 1/0 BIT, or raise an error if not possible. Previously the expression would return NULL.

    References: #11074

  • [mssql] [bug]

    Fix mssql+pyodbc issue where valid plus signs in an already-unquoted odbc_connect= (raw DBAPI) connection string are replaced with spaces.

    The pyodbc connector would unconditionally pass the odbc_connect value to unquote_plus(), even if it was not required. So, if the (unquoted) odbc_connect value contained PWD=pass+word that would get changed to PWD=pass word, and the login would fail. One workaround was to quote just the plus sign — PWD=pass%2Bword — which would then get unquoted to PWD=pass+word.

    References: #11250

tests

  • [tests] [change]

    The top-level test runner has been changed to use nox, adding a noxfile.py as well as some included modules. The tox.ini file remains in place so that tox runs will continue to function in the near term, however it will be eventually removed and improvements and maintenance going forward will be only towards noxfile.py.

misc

  • [misc] [changed]

    Removed multiple api that were deprecated in the 1.3 series and earlier. The list of removed features includes:

    • The force parameter of IdentifierPreparer.quote and IdentifierPreparer.quote_schema;

    • The threaded parameter of the cx-Oracle dialect;

    • The _json_serializer and _json_deserializer parameters of the SQLite dialect;

    • The collection.converter decorator;

    • The Mapper.mapped_table property;

    • The Session.close_all method;

    • Support for multiple arguments in defer() and undefer().

    References: #12441