1.. _migration_20_toplevel: 2 3============================= 4Migrating to SQLAlchemy 2.0 5============================= 6 7.. admonition:: About this document 8 9 SQLAlchemy 2.0 presents a major shift for a wide variety of key 10 SQLAlchemy usage patterns in both the Core and ORM components. The goal 11 of this release is to make a slight readjustment in some of the most 12 fundamental assumptions of SQLAlchemy since its early beginnings, and to 13 deliver a newly streamlined usage model that is hoped to be significantly 14 more minimalist and consistent between the Core and ORM components, as well 15 as more capable. The move of Python to be Python 3 only as well as the 16 emergence of gradual typing systems for Python 3 are the initial 17 inspirations for this shift, as is the changing nature of the Python 18 community which now includes not just hardcore database programmers but a 19 vast new community of data scientists and students of many different 20 disciplines. 21 22 SQLAlchemy started with Python 2.3 which had no context managers, no 23 function decorators, Unicode as a second class feature, and a variety of 24 other shortcomings that would be unknown today. The biggest changes in 25 SQLAlchemy 2.0 are targeting the residual assumptions left over from this 26 early period in SQLAlchemy's development as well as the leftover artifacts 27 resulting from the incremental introduction of key API features such as 28 :class:`.orm.query.Query` and Declarative. It also hopes standardize some 29 newer capabilities that have proven to be very effective. 30 31 32Overview 33======== 34 35The SQLAlchemy 2.0 transition presents itself in the SQLAlchemy 1.4 release as 36a series of steps that allow an application of any size or complexity to be 37migrated to SQLAlchemy 2.0 using a gradual, iterative process. Lessons learned 38from the Python 2 to Python 3 transition have inspired a system that intends to 39as great a degree as possible to not require any "breaking" changes, or any 40change that would need to be made universally or not at all. 41 42As a means of both proving the 2.0 architecture as well as allowing a fully 43iterative transition environment, the entire scope of 2.0's new APIs and 44features are present and available within the 1.4 series; this includes 45major new areas of functionality such as the SQL caching system, the new ORM 46statement execution model, new transactional paradigms for both ORM and Core, a 47new ORM declarative system that unifies classical and declarative mapping, 48support for Python dataclasses, and asyncio support for Core and ORM. 49 50The steps to achieve 2.0 migration are in the following subsections; overall, 51the general strategy is that once an application runs on 1.4 with all 52warning flags turned on and does not emit any 2.0-deprecation warnings, it is 53now cross-compatible with SQLAlchemy 2.0. 54 55 56First Prerequisite, step one - A Working 1.3 Application 57--------------------------------------------------------- 58 59The first step is getting an existing application onto 1.4, in the case of 60a typical non trivial application, is to ensure it runs on SQLAlchemy 1.3 with 61no deprecation warnings. Release 1.4 does have a few changes linked to 62conditions that warn in previous version, including some warnings that were 63introduced in 1.3, in particular some changes to the behavior of the 64:paramref:`_orm.relationship.viewonly` and 65:paramref:`_orm.relationship.sync_backref` flags. 66 67For best results, the application should be able to run, or pass all of its 68tests, with the latest SQLAlchemy 1.3 release with no SQLAlchemy deprecation 69warnings; these are warnings emitted for the :class:`_exc.SADeprecationWarning` 70class. 71 72First Prerequisite, step two - A Working 1.4 Application 73-------------------------------------------------------- 74 75Once the application is good to go on SQLAlchemy 1.3, the next step is to get 76it running on SQLAlchemy 1.4. In the vast majority of cases, applications 77should run without problems from SQLAlchemy 1.3 to 1.4. However, it's always 78the case between any 1.x and 1.y release, APIs and behaviors have changed 79either subtly or in some cases a little less subtly, and the SQLAlchemy 80project always gets a good deal of regression reports for the first few 81months. 82 83The 1.x->1.y release process usually has a few changes around the margins 84that are a little bit more dramatic and are based around use cases that are 85expected to be very seldom if at all used. For 1.4, the changes identified 86as being in this realm are as follows: 87 88* :ref:`change_5526` - this impacts code that would be manipulating the 89 :class:`_engine.URL` object and may impact code that makes use of the 90 :class:`_engine.CreateEnginePlugin` extension point. This is an uncommon 91 case but may affect in particular some test suites that are making use of 92 special database provisioning logic. A github search for code that uses 93 the relatively new and little-known :class:`_engine.CreateEnginePlugin` 94 class found two projects that were unaffected by the change. 95 96* :ref:`change_4617` - this change may impact code that was somehow relying 97 upon behavior that was mostly unusable in the :class:`_sql.Select` construct, 98 where it would create unnamed subqueries that were usually confusing and 99 non-working. These subqueries would be rejected by most databases in any 100 case as a name is usually required except on SQLite, however it is possible 101 some applications will need to adjust some queries that are inadvertently 102 relying upon this. 103 104* :ref:`change_select_join` - somewhat related, the :class:`_sql.Select` class 105 featured ``.join()`` and ``.outerjoin()`` methods that implicitly created a 106 subquery and then returned a :class:`_sql.Join` construct, which again would 107 be mostly useless and produced lots of confusion. The decision was made to 108 move forward with the vastly more useful 2.0-style join-building approach 109 where these methods now work the same way as the ORM :meth:`_orm.Query.join` 110 method. 111 112* :ref:`change_deferred_construction` - some error messages related to 113 construction of a :class:`_orm.Query` or :class:`_sql.Select` may not be 114 emitted until compilation / execution, rather than at construction time. 115 This might impact some test suites that are testing against failure modes. 116 117For the full overview of SQLAlchemy 1.4 changes, see the 118:doc:`/changelog/migration_14` document. 119 120Migration to 2.0 Step One - Python 3 only (Python 3.6 minimum) 121-------------------------------------------------------------- 122 123SQLAlchemy 2.0 was first inspired by the fact that Python 2's EOL was in 1242020. SQLAlchemy is taking a longer period of time than other major 125projects to drop Python 2.7 support, since it is not too much in the way 126of things for the moment. However, version 2.0 hopes to start embracing 127:pep:`484` and other new features to a great degree, so it is likely 128that release 1.4 will be the last Python 2 supporting version, even if 129there is a SQLAlchemy 1.5 (which is also unlikely at the moment). 130 131In order to use SQLAlchemy 2.0, the application will need to be runnable on 132at least **Python 3.6** as of this writing. SQLAlchemy 1.4 now supports 133Python 3.6 or newer within the Python 3 series; throughout the 1.4 series, 134the application can remain running on Python 2.7 or on at least Python 3.6. 135 136.. _migration_20_deprecations_mode: 137 138Migration to 2.0 Step Two - Turn on RemovedIn20Warnings 139------------------------------------------------------- 140 141SQLAlchemy 1.4 features a conditional deprecation warning system inspired 142by the Python "-3" flag that would indicate legacy patterns in a running 143application. For SQLAlchemy 1.4, the :class:`_exc.RemovedIn20Warning` 144deprecation class is emitted only when an environment variable 145``SQLALCHEMY_WARN_20`` is set to either of ``true`` or ``1``. 146 147Given the example program below:: 148 149 from sqlalchemy import column 150 from sqlalchemy import create_engine 151 from sqlalchemy import select 152 from sqlalchemy import table 153 154 155 engine = create_engine("sqlite://") 156 157 engine.execute("CREATE TABLE foo (id integer)") 158 engine.execute("INSERT INTO foo (id) VALUES (1)") 159 160 161 foo = table("foo", column("id")) 162 result = engine.execute(select([foo.c.id])) 163 164 print(result.fetchall()) 165 166The above program uses several patterns that many users will already identify 167as "legacy", namely the use of the :meth:`_engine.Engine.execute` method 168that's part of the :ref:`connectionless execution <dbengine_implicit>` 169system. When we run the above program against 1.4, it returns a single line:: 170 171 $ python test3.py 172 [(1,)] 173 174To enable "2.0 deprecations mode", we enable the ``SQLALCHEMY_WARN_20=1`` 175variable, and additionally ensure that a `warnings filter`_ that will not 176suppress any warnings is selected:: 177 178 SQLALCHEMY_WARN_20=1 python -W always::DeprecationWarning test3.py 179 180Since the reported warning location is not always in the correct place, locating 181the offending code may be difficult without the full stacktrace. This can be achieved 182by transforming the warnings to exceptions by specifying the ``error`` warning filter, 183using Python option ``-W error::DeprecationWarning``. 184 185.. _warnings filter: https://docs.python.org/3/library/warnings.html#the-warnings-filter 186 187With warnings turned on, our program now has a lot to say:: 188 189 $ SQLALCHEMY_WARN_20=1 python2 -W always::DeprecationWarning test3.py 190 test3.py:9: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) 191 engine.execute("CREATE TABLE foo (id integer)") 192 /home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:2856: RemovedIn20Warning: Passing a string to Connection.execute() is deprecated and will be removed in version 2.0. Use the text() construct, or the Connection.exec_driver_sql() method to invoke a driver-level SQL string. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) 193 return connection.execute(statement, *multiparams, **params) 194 /home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:1639: RemovedIn20Warning: The current statement is being autocommitted using implicit autocommit.Implicit autocommit will be removed in SQLAlchemy 2.0. Use the .begin() method of Engine or Connection in order to use an explicit transaction for DML and DDL statements. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) 195 self._commit_impl(autocommit=True) 196 test3.py:10: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) 197 engine.execute("INSERT INTO foo (id) VALUES (1)") 198 /home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:2856: RemovedIn20Warning: Passing a string to Connection.execute() is deprecated and will be removed in version 2.0. Use the text() construct, or the Connection.exec_driver_sql() method to invoke a driver-level SQL string. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) 199 return connection.execute(statement, *multiparams, **params) 200 /home/classic/dev/sqlalchemy/lib/sqlalchemy/engine/base.py:1639: RemovedIn20Warning: The current statement is being autocommitted using implicit autocommit.Implicit autocommit will be removed in SQLAlchemy 2.0. Use the .begin() method of Engine or Connection in order to use an explicit transaction for DML and DDL statements. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) 201 self._commit_impl(autocommit=True) 202 /home/classic/dev/sqlalchemy/lib/sqlalchemy/sql/selectable.py:4271: RemovedIn20Warning: The legacy calling style of select() is deprecated and will be removed in SQLAlchemy 2.0. Please use the new calling style described at select(). (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) 203 return cls.create_legacy_select(*args, **kw) 204 test3.py:14: RemovedIn20Warning: The Engine.execute() function/method is considered legacy as of the 1.x series of SQLAlchemy and will be removed in 2.0. All statement execution in SQLAlchemy 2.0 is performed by the Connection.execute() method of Connection, or in the ORM by the Session.execute() method of Session. (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) (Background on SQLAlchemy 2.0 at: https://sqlalche.me/e/b8d9) 205 result = engine.execute(select([foo.c.id])) 206 [(1,)] 207 208With the above guidance, we can migrate our program to use 2.0 styles, and 209as a bonus our program is much clearer:: 210 211 from sqlalchemy import column 212 from sqlalchemy import create_engine 213 from sqlalchemy import select 214 from sqlalchemy import table 215 from sqlalchemy import text 216 217 218 engine = create_engine("sqlite://") 219 220 # don't rely on autocommit for DML and DDL 221 with engine.begin() as connection: 222 # use connection.execute(), not engine.execute() 223 # use the text() construct to execute textual SQL 224 connection.execute(text("CREATE TABLE foo (id integer)")) 225 connection.execute(text("INSERT INTO foo (id) VALUES (1)")) 226 227 228 foo = table("foo", column("id")) 229 230 with engine.connect() as connection: 231 # use connection.execute(), not engine.execute() 232 # select() now accepts column / table expressions positionally 233 result = connection.execute(select(foo.c.id)) 234 235 print(result.fetchall()) 236 237 238The goal of "2.0 deprecations mode" is that a program which runs with no 239:class:`_exc.RemovedIn20Warning` warnings with "2.0 deprecations mode" turned 240on is then ready to run in SQLAlchemy 2.0. 241 242 243Migration to 2.0 Step Three - Resolve all RemovedIn20Warnings 244-------------------------------------------------------------- 245 246Code can be developed iteratively to resolve these warnings. Within 247the SQLAlchemy project itself, the approach taken is as follows: 248 2491. enable the ``SQLALCHEMY_WARN_20=1`` environment variable in the test suite, 250 for SQLAlchemy this is in the tox.ini file 251 2522. Within the setup for the test suite, set up a series of warnings filters 253 that will select for particular subsets of warnings to either raise an 254 exception, or to be ignored (or logged). Work with just one subgroup of warnings 255 at a time. Below, a warnings filter is configured for an application where 256 the change to the Core level ``.execute()`` calls will be needed in order 257 for all tests to pass, but all other 2.0-style warnings will be suppressed: 258 259 .. sourcecode:: 260 261 import warnings 262 from sqlalchemy import exc 263 264 # for warnings not included in regex-based filter below, just log 265 warnings.filterwarnings( 266 "always", category=exc.RemovedIn20Warning 267 ) 268 269 # for warnings related to execute() / scalar(), raise 270 for msg in [ 271 r"The (?:Executable|Engine)\.(?:execute|scalar)\(\) function", 272 r"The current statement is being autocommitted using implicit " 273 "autocommit,", 274 r"The connection.execute\(\) method in SQLAlchemy 2.0 will accept " 275 "parameters as a single dictionary or a single sequence of " 276 "dictionaries only.", 277 r"The Connection.connect\(\) function/method is considered legacy", 278 r".*DefaultGenerator.execute\(\)", 279 ]: 280 warnings.filterwarnings( 281 "error", message=msg, category=exc.RemovedIn20Warning, 282 ) 283 2843. As each sub-category of warnings are resolved in the application, new 285 warnings that are caught by the "always" filter can be added to the list 286 of "errors" to be resolved. 287 2884. Once no more warnings are emitted, the filter can be removed. 289 290Migration to 2.0 Step Four - Use the ``future`` flag on Engine 291-------------------------------------------------------------- 292 293The :class:`_engine.Engine` object features an updated 294transaction-level API in version 2.0. In 1.4, this new API is available 295by passing the flag ``future=True`` to the :func:`_sa.create_engine` 296function. 297 298When the :paramref:`_sa.create_engine.future` flag is used, the :class:`_future.Engine` 299and :class:`_future.Connection` objects support the 2.0 API fully and not at all 300any legacy features, including the new argument format for :meth:`_future.Connection.execute`, 301the removal of "implicit autocommit", string statements require the 302:func:`_sql.text` construct unless the :meth:`_future.Connection.exec_driver_sql` 303method is used, and connectionless execution from the :class:`_future.Engine` 304is removed. 305 306If all :class:`_exc.RemovedIn20Warning` warnings have been resolved regarding 307use of the :class:`_engine.Engine` and :class:`_engine.Connection`, then the 308:paramref:`_sa.create_engine.future` flag may be enabled and there should be 309no errors raised. 310 311The new engine is described at :class:`_future.Engine` which delivers a new 312:class:`_future.Connection` object. In addition to the above changes, the, 313:class:`_future.Connection` object features 314:meth:`_future.Connection.commit` and 315:meth:`_future.Connection.rollback` methods, to support the new 316"commit-as-you-go" mode of operation:: 317 318 319 from sqlalchemy import create_engine 320 321 engine = create_engine("postgresql:///") 322 323 with engine.connect() as conn: 324 conn.execute(text("insert into table (x) values (:some_x)"), {"some_x": 10}) 325 326 conn.commit() # commit as you go 327 328 329 330Migration to 2.0 Step Four - Use the ``future`` flag on Session 331--------------------------------------------------------------- 332 333The :class:`_orm.Session` object also features an updated transaction/connection 334level API in version 2.0. This API is available in 1.4 using the 335:paramref:`_orm.Session.future` flag on :class:`_orm.Session` or on 336:class:`_orm.sessionmaker`. 337 338The :class:`_orm.Session` object supports "future" mode in place, and involves 339these changes: 340 3411. The :class:`_orm.Session` no longer supports "bound metadata" when it 342 resolves the engine to be used for connectivity. This means that an 343 :class:`_engine.Engine` object **must** be passed to the constructor (this 344 may be either a legacy or future style object). 345 3462. The :paramref:`_orm.Session.begin.subtransactions` flag is no longer 347 supported. 348 3493. The :meth:`_orm.Session.commit` method always emits a COMMIT to the database, 350 rather than attempting to reconcile "subtransactions". 351 3524. The :meth:`_orm.Session.rollback` method always rolls back the full 353 stack of transactions at once, rather than attempting to keep 354 "subtransactions" in place. 355 356 357The :class:`_orm.Session` also supports more flexible creational patterns 358in 1.4 which are now closely matched to the patterns used by the 359:class:`_engine.Connection` object. Highlights include that the 360:class:`_orm.Session` may be used as a context manager:: 361 362 from sqlalchemy.orm import Session 363 with Session(engine) as session: 364 session.add(MyObject()) 365 session.commit() 366 367In addition, the :class:`_orm.sessionmaker` object supports a 368:meth:`_orm.sessionmaker.begin` context manager that will create a 369:class:`_orm.Session` and begin /commit a transaction in one block:: 370 371 from sqlalchemy.orm import sessionmaker 372 373 Session = sessionmaker(engine) 374 375 with Session.begin() as session: 376 session.add(MyObject()) 377 378See the section :ref:`orm_session_vs_engine` for a comparison of 379:class:`_orm.Session` creational patterns compared to those of 380:class:`_engine.Connection`. 381 382Once the application passes all tests/ runs with ``SQLALCHEMY_WARN_20=1`` 383and all ``exc.RemovedIn20Warning`` occurrences set to raise an error, 384**the application is ready!**. 385 386The sections that follow will detail the specific changes to make for all 387major API modifications. 388 389 3902.0 Migration - Core Connection / Transaction 391============================================= 392 393 394.. _migration_20_autocommit: 395 396Library-level (but not driver level) "Autocommit" removed from both Core and ORM 397-------------------------------------------------------------------------------- 398 399**Synopsis** 400 401In SQLAlchemy 1.x, the following statements will automatically commit 402the underlying DBAPI transaction, but in SQLAlchemy 4032.0 this will not occur:: 404 405 conn = engine.connect() 406 407 # won't autocommit in 2.0 408 conn.execute(some_table.insert().values(foo='bar')) 409 410Nor will this autocommit:: 411 412 conn = engine.connect() 413 414 # won't autocommit in 2.0 415 conn.execute(text("INSERT INTO table (foo) VALUES ('bar')")) 416 417The common workaround for custom DML that requires commit, the "autocommit" 418execution option, will be removed:: 419 420 421 conn = engine.connect() 422 423 # won't autocommit in 2.0 424 conn.execute( 425 text("EXEC my_procedural_thing()").execution_options(autocommit=True) 426 ) 427 428 429**Migration to 2.0** 430 431The method that is cross-compatible with :term:`1.x style` and :term:`2.0 432style` execution is to make use of the :meth:`_engine.Connection.begin` method, 433or the :meth:`_engine.Engine.begin` context manager:: 434 435 with engine.begin() as conn: 436 conn.execute(some_table.insert().values(foo='bar')) 437 conn.execute(some_other_table.insert().values(bat='hoho')) 438 439 with engine.connect() as conn: 440 with conn.begin(): 441 conn.execute(some_table.insert().values(foo='bar')) 442 conn.execute(some_other_table.insert().values(bat='hoho')) 443 444 with engine.begin() as conn: 445 conn.execute(text("EXEC my_procedural_thing()")) 446 447When using :term:`2.0 style` with the :paramref:`_sa.create_engine.future` 448flag, "commit as you go" style may also be used, as the 449:class:`_future.Connection` features **autobegin** behavior, which takes place 450when a statement is first invoked in the absence of an explicit call to 451:meth:`_future.Connection.begin`:: 452 453 with engine.connect() as conn: 454 conn.execute(some_table.insert().values(foo='bar')) 455 conn.execute(some_other_table.insert().values(bat='hoho')) 456 457 conn.commit() 458 459When :ref:`2.0 deprecations mode <migration_20_deprecations_mode>` is enabled, 460a warning will emit when the deprecated "autocommit" feature takes place, 461indicating those places where an explicit transaction should be noted. 462 463 464**Discussion** 465 466SQLAlchemy's first releases were at odds with the spirit of the Python DBAPI 467(:pep:`249`) in that it tried to hide :pep:`249`'s emphasis on "implicit begin" 468and "explicit commit" of transactions. Fifteen years later we now see this 469was essentially a mistake, as SQLAlchemy's many patterns that attempt to "hide" 470the presence of a transaction make for a more complex API which works 471inconsistently and is extremely confusing to especially those users who are new 472to relational databases and ACID transactions in general. SQLAlchemy 2.0 will 473do away with all attempts to implicitly commit transactions, and usage patterns 474will always require that the user demarcate the "beginning" and the "end" of a 475transaction in some way, in the same way as reading or writing to a file in 476Python has a "beginning" and an "end". 477 478In the case of autocommit for a pure textual statement, there is actually a 479regular expression that parses every statement in order to detect autocommit! 480Not surprisingly, this regex is continuously failing to accommodate for various 481kinds of statements and stored procedures that imply a "write" to the 482database, leading to ongoing confusion as some statements produce results in 483the database and others don't. By preventing the user from being aware of the 484transactional concept, we get a lot of bug reports on this one because users 485don't understand that databases always use a transaction, whether or not some 486layer is autocommitting it. 487 488SQLAlchemy 2.0 will require that all database actions at every level be 489explicit as to how the transaction should be used. For the vast majority 490of Core use cases, it's the pattern that is already recommended:: 491 492 with engine.begin() as conn: 493 conn.execute(some_table.insert().values(foo='bar')) 494 495For "commit as you go, or rollback instead" usage, which resembles how the 496:class:`_orm.Session` is normally used today, the "future" version of 497:class:`_future.Connection`, which is the one that is returned from an 498:class:`_future.Engine` that was created using the 499:paramref:`_sa.create_engine.future` flag, includes new 500:meth:`_future.Connection.commit` and :meth:`_future.Connection.rollback` 501methods, which act upon a transaction that is now begun automatically when 502a statement is first invoked:: 503 504 # 1.4 / 2.0 code 505 506 from sqlalchemy import create_engine 507 508 engine = create_engine(..., future=True) 509 510 with engine.connect() as conn: 511 conn.execute(some_table.insert().values(foo='bar')) 512 conn.commit() 513 514 conn.execute(text("some other SQL")) 515 conn.rollback() 516 517Above, the ``engine.connect()`` method will return a :class:`_engine.Connection` that 518features **autobegin**, meaning the ``begin()`` event is emitted when the 519execute method is first used (note however that there is no actual "BEGIN" in 520the Python DBAPI). "autobegin" is a new pattern in SQLAlchemy 1.4 that 521is featured both by :class:`_future.Connection` as well as the ORM 522:class:`_orm.Session` object; autobegin allows that the :meth:`_future.Connection.begin` 523method may be called explicitly when the object is first acquired, for schemes 524that wish to demarcate the beginning of the transaction, but if the method 525is not called, then it occurs implicitly when work is first done on the object. 526 527The removal of "autocommit" is closely related to the removal of 528"connectionless" execution discussed at :ref:`migration_20_implicit_execution`. 529All of these legacy patterns built up from the fact that Python did not have 530context managers or decorators when SQLAlchemy was first created, so there were 531no convenient idiomatic patterns for demarcating the use of a resource. 532 533Driver-level autocommit remains available 534^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 535 536True "autocommit" behavior is now widely available with most DBAPI 537implementations, and is supported by SQLAlchemy via the 538:paramref:`_engine.Connection.execution_options.isolation_level` parameter as 539discussed at :ref:`dbapi_autocommit`. True autocommit is treated as an "isolation level" 540so that the structure of application code does not change when autocommit is 541used; the :meth:`_engine.Connection.begin` context manager as well as 542methods like :meth:`_future.Connection.commit` may still be used, they are 543simply no-ops at the database driver level when DBAPI-level autocommit 544is turned on. 545 546.. _migration_20_implicit_execution: 547 548"Implicit" and "Connectionless" execution, "bound metadata" removed 549-------------------------------------------------------------------- 550 551**Synopsis** 552 553The ability to associate an :class:`_engine.Engine` with a :class:`_schema.MetaData` 554object, which then makes available a range of so-called "connectionless" 555execution patterns, is removed:: 556 557 from sqlalchemy import MetaData 558 559 metadata_obj = MetaData(bind=engine) # no longer supported 560 561 metadata_obj.create_all() # requires Engine or Connection 562 563 metadata_obj.reflect() # requires Engine or Connection 564 565 t = Table('t', metadata_obj, autoload=True) # use autoload_with=engine 566 567 result = engine.execute(t.select()) # no longer supported 568 569 result = t.select().execute() # no longer supported 570 571**Migration to 2.0** 572 573For schema level patterns, explicit use of an :class:`_engine.Engine` 574or :class:`_engine.Connection` is required. The :class:`_engine.Engine` 575may still be used directly as the source of connectivity for a 576:meth:`_schema.MetaData.create_all` operation or autoload operation. 577For executing statements, only the :class:`_engine.Connection` object 578has a :meth:`_engine.Connection.execute` method (in addition to 579the ORM-level :meth:`_orm.Session.execute` method):: 580 581 582 from sqlalchemy import MetaData 583 584 metadata_obj = MetaData() 585 586 # engine level: 587 588 # create tables 589 metadata_obj.create_all(engine) 590 591 # reflect all tables 592 metadata_obj.reflect(engine) 593 594 # reflect individual table 595 t = Table('t', metadata_obj, autoload_with=engine) 596 597 598 # connection level: 599 600 601 with engine.connect() as connection: 602 # create tables, requires explicit begin and/or commit: 603 with connection.begin(): 604 metadata_obj.create_all(connection) 605 606 # reflect all tables 607 metadata_obj.reflect(connection) 608 609 # reflect individual table 610 t = Table('t', metadata_obj, autoload_with=connection) 611 612 # execute SQL statements 613 result = conn.execute(t.select()) 614 615 616**Discussion** 617 618 619The Core documentation has already standardized on the desired pattern here, 620so it is likely that most modern applications would not have to change 621much in any case, however there are likely many applications that still 622rely upon ``engine.execute()`` calls that will need to be adjusted. 623 624"Connectionless" execution refers to the still fairly popular pattern of 625invoking ``.execute()`` from the :class:`_engine.Engine`:: 626 627 result = engine.execute(some_statement) 628 629The above operation implicitly procures a :class:`_engine.Connection` object, 630and runs the ``.execute()`` method on it. While this appears to be a simple 631convenience feature, it has been shown to give rise to several issues: 632 633* Programs that feature extended strings of ``engine.execute()`` calls have 634 become prevalent, overusing a feature that was intended to be seldom used and 635 leading to inefficient non-transactional applications. New users are 636 confused as to the difference between ``engine.execute()`` and 637 ``connection.execute()`` and the nuance between these two approaches is 638 often not understood. 639 640* The feature relies upon the "application level autocommit" feature in order 641 to make sense, which itself is also being removed as it is also 642 :ref:`inefficient and misleading <migration_20_autocommit>`. 643 644* In order to handle result sets, ``Engine.execute`` returns a result object 645 with unconsumed cursor results. This cursor result necessarily still links 646 to the DBAPI connection which remains in an open transaction, all of which is 647 released once the result set has fully consumed the rows waiting within the 648 cursor. This means that ``Engine.execute`` does not actually close out the 649 connection resources that it claims to be managing when the call is complete. 650 SQLAlchemy's "autoclose" behavior is well-tuned enough that users don't 651 generally report any negative effects from this system, however it remains 652 an overly implicit and inefficient system left over from SQLAlchemy's 653 earliest releases. 654 655The removal of "connectionless" execution then leads to the removal of 656an even more legacy pattern, that of "implicit, connectionless" execution:: 657 658 result = some_statement.execute() 659 660The above pattern has all the issues of "connectionless" execution, plus it 661relies upon the "bound metadata" pattern, which SQLAlchemy has tried to 662de-emphasize for many years. This was SQLAlchemy's very first advertised 663usage model in version 0.1, which became obsolete almost immediately when 664the :class:`_engine.Connection` object was introduced and later Python 665context managers provided a better pattern for using resources within a 666fixed scope. 667 668With implicit execution removed, "bound metadata" itself also no longer has 669a purpose within this system. In modern use "bound metadata" tends to still 670be somewhat convenient for working within :meth:`_schema.MetaData.create_all` 671calls as well as with :class:`_orm.Session` objects, however having these 672functions receive an :class:`_engine.Engine` explicitly provides for clearer 673application design. 674 675Many Choices becomes One Choice 676^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 677 678Overall, the above executional patterns were introduced in SQLAlchemy's 679very first 0.1 release before the :class:`_engine.Connection` object even existed. 680After many years of de-emphasizing these patterns, "implicit, connectionless" 681execution and "bound metadata" are no longer as widely used so in 2.0 we seek 682to finally reduce the number of choices for how to execute a statement in 683Core from "many choices":: 684 685 # many choices 686 687 # bound metadata? 688 metadata_obj = MetaData(engine) 689 690 # or not? 691 metadata_obj = MetaData() 692 693 # execute from engine? 694 result = engine.execute(stmt) 695 696 # or execute the statement itself (but only if you did 697 # "bound metadata" above, which means you can't get rid of "bound" if any 698 # part of your program uses this form) 699 result = stmt.execute() 700 701 # execute from connection, but it autocommits? 702 conn = engine.connect() 703 conn.execute(stmt) 704 705 # execute from connection, but autocommit isn't working, so use the special 706 # option? 707 conn.execution_options(autocommit=True).execute(stmt) 708 709 # or on the statement ?! 710 conn.execute(stmt.execution_options(autocommit=True)) 711 712 # or execute from connection, and we use explicit transaction? 713 with conn.begin(): 714 conn.execute(stmt) 715 716to "one choice", where by "one choice" we mean "explicit connection with 717explicit transaction"; there are still a few ways to demarcate 718transaction blocks depending on need. The "one choice" is to procure a 719:class:`_engine.Connection` and then to explicitly demarcate the transaction, 720in the case that the operation is a write operation:: 721 722 # one choice - work with explicit connection, explicit transaction 723 # (there remain a few variants on how to demarcate the transaction) 724 725 # "begin once" - one transaction only per checkout 726 with engine.begin() as conn: 727 result = conn.execute(stmt) 728 729 # "commit as you go" - zero or more commits per checkout 730 with engine.connect() as conn: 731 result = conn.execute(stmt) 732 conn.commit() 733 734 # "commit as you go" but with a transaction block instead of autobegin 735 with engine.connect() as conn: 736 with conn.begin(): 737 result = conn.execute(stmt) 738 739 740execute() method more strict, execution options are more prominent 741------------------------------------------------------------------------------- 742 743**Synopsis** 744 745The argument patterns that may be used with the :meth:`_engine.Connection` 746execute method in SQLAlchemy 2.0 are highly simplified, removing many previously 747available argument patterns. The new API in the 1.4 series is described at 748:meth:`_future.Connection`. The examples below illustrate the patterns that 749require modification:: 750 751 752 connection = engine.connect() 753 754 # direct string SQL not supported; use text() or exec_driver_sql() method 755 result = connection.execute("select * from table") 756 757 # positional parameters no longer supported, only named 758 # unless using exec_driver_sql() 759 result = connection.execute(table.insert(), ('x', 'y', 'z')) 760 761 # **kwargs no longer accepted, pass a single dictionary 762 result = connection.execute(table.insert(), x=10, y=5) 763 764 # multiple *args no longer accepted, pass a list 765 result = connection.execute( 766 table.insert(), 767 {"x": 10, "y": 5}, {"x": 15, "y": 12}, {"x": 9, "y": 8} 768 ) 769 770 771**Migration to 2.0** 772 773The new :meth:`_future.Connection.execute` method now accepts a subset of the 774argument styles that are accepted by the 1.x :meth:`_engine.Connection.execute` 775method, so the following code is cross-compatible between 1.x and 2.0:: 776 777 778 connection = engine.connect() 779 780 from sqlalchemy import text 781 result = connection.execute(text("select * from table")) 782 783 # pass a single dictionary for single statement execution 784 result = connection.execute(table.insert(), {"x": 10, "y": 5}) 785 786 # pass a list of dictionaries for executemany 787 result = connection.execute( 788 table.insert(), 789 [{"x": 10, "y": 5}, {"x": 15, "y": 12}, {"x": 9, "y": 8}] 790 ) 791 792 793 794**Discussion** 795 796The use of ``*args`` and ``**kwargs`` has been removed both to remove the 797complexity of guessing what kind of arguments were passed to the method, as 798well as to make room for other options, namely the 799:paramref:`_future.Connection.execute.execution_options` dictionary that is now 800available to provide options on a per statement basis. The method is also 801modified so that its use pattern matches that of the 802:meth:`_orm.Session.execute` method, which is a much more prominent API in 2.0 803style. 804 805The removal of direct string SQL is to resolve an inconsistency between 806:meth:`_engine.Connection.execute` and :meth:`_orm.Session.execute`, 807where in the former case the string is passed to the driver raw, and in the 808latter case it is first converted to a :func:`_sql.text` construct. By 809allowing only :func:`_sql.text` this also limits the accepted parameter 810format to "named" and not "positional". Finally, the string SQL use case 811is becoming more subject to scrutiny from a security perspective, and 812the :func:`_sql.text` construct has come to represent an explicit boundary 813into the textual SQL realm where attention to untrusted user input must be 814given. 815 816 817.. _migration_20_result_rows: 818 819Result rows act like named tuples 820--------------------------------- 821 822**Synopsis** 823 824Version 1.4 introduces an :ref:`all new Result object <change_result_14_core>` 825that in turn returns :class:`_engine.Row` objects, which behave like named 826tuples when using "future" mode:: 827 828 engine = create_engine(..., future=True) # using future mode 829 830 with engine.connect() as conn: 831 result = conn.execute(text("select x, y from table")) 832 833 row = result.first() # suppose the row is (1, 2) 834 835 "x" in row # evaluates to False, in 1.x / future=False, this would be True 836 837 1 in row # evaluates to True, in 1.x / future=False, this would be False 838 839 840**Migration to 2.0** 841 842Application code or test suites that are testing for a particular key 843being present in a row would need to test the ``row.keys()`` collection 844instead. This is however an unusual use case as a result row is typically 845used by code that already knows what columns are present within it. 846 847**Discussion** 848 849Already part of 1.4, the previous ``KeyedTuple`` class that was used when 850selecting rows from the :class:`_query.Query` object has been replaced by the 851:class:`.Row` class, which is the base of the same :class:`.Row` that comes 852back with Core statement results when using the 853:paramref:`_sa.create_engine.future` flag with :class:`_engine.Engine` (when 854the :paramref:`_sa.create_engine.future` flag is not set, Core result sets use 855the :class:`.LegacyRow` subclass, which maintains backwards-compatible 856behaviors for the ``__contains__()`` method; ORM exclusively uses the 857:class:`.Row` class directly). 858 859This :class:`.Row` behaves like a named tuple, in that it acts as a sequence 860but also supports attribute name access, e.g. ``row.some_column``. However, 861it also provides the previous "mapping" behavior via the special attribute 862``row._mapping``, which produces a Python mapping such that keyed access 863such as ``row["some_column"]`` can be used. 864 865In order to receive results as mappings up front, the ``mappings()`` modifier 866on the result can be used:: 867 868 from sqlalchemy.future.orm import Session 869 870 session = Session(some_engine) 871 872 result = session.execute(stmt) 873 for row in result.mappings(): 874 print("the user is: %s" % row["User"]) 875 876The :class:`.Row` class as used by the ORM also supports access via entity 877or attribute:: 878 879 from sqlalchemy.future import select 880 881 stmt = select(User, Address).join(User.addresses) 882 883 for row in session.execute(stmt).mappings(): 884 print("the user is: %s the address is: %s" % ( 885 row[User], 886 row[Address] 887 )) 888 889.. seealso:: 890 891 :ref:`change_4710_core` 892 893 8942.0 Migration - Core Usage 895============================= 896 897.. _migration_20_5284: 898 899select() no longer accepts varied constructor arguments, columns are passed positionally 900----------------------------------------------------------------------------------------- 901 902**synopsis** 903 904The :func:`_sql.select` construct as well as the related method :meth:`_sql.FromClause.select` 905will no longer accept keyword arguments to build up elements such as the 906WHERE clause, FROM list and ORDER BY. The list of columns may now be 907sent positionally, rather than as a list. Additionally, the :func:`_sql.case` construct 908now accepts its WHEN criteria positionally, rather than as a list:: 909 910 # select_from / order_by keywords no longer supported 911 stmt = select([1], select_from=table, order_by=table.c.id) 912 913 # whereclause parameter no longer supported 914 stmt = select([table.c.x], table.c.id == 5) 915 916 # whereclause parameter no longer supported 917 stmt = table.select(table.c.id == 5) 918 919 # list emits a deprecation warning 920 stmt = select([table.c.x, table.c.y]) 921 922 # list emits a deprecation warning 923 case_clause = case( 924 [ 925 (table.c.x == 5, "five"), 926 (table.c.x == 7, "seven") 927 ], 928 else_="neither five nor seven" 929 ) 930 931 932**Migration to 2.0** 933 934Only the "generative" style of :func:`_sql.select` will be supported. The list 935of columns / tables to SELECT from should be passed positionally. The 936:func:`_sql.select` construct in SQLAlchemy 1.4 accepts both the legacy 937styles and the new styles using an auto-detection scheme, so the code below 938is cross-compatible with 1.4 and 2.0:: 939 940 # use generative methods 941 stmt = select(1).select_from(table).order_by(table.c.id) 942 943 # use generative methods 944 stmt = select(table).where(table.c.id == 5) 945 946 # use generative methods 947 stmt = table.select().where(table.c.id == 5) 948 949 # pass columns clause expressions positionally 950 stmt = select(table.c.x, table.c.y) 951 952 # case conditions passed positionally 953 case_clause = case( 954 (table.c.x == 5, "five"), 955 (table.c.x == 7, "seven"), 956 else_="neither five nor seven" 957 ) 958 959**Discussion** 960 961SQLAlchemy has for many years developed a convention for SQL constructs 962accepting an argument either as a list or as positional arguments. This 963convention states that **structural** elements, those that form the structure 964of a SQL statement, should be passed **positionally**. Conversely, 965**data** elements, those that form the parameterized data of a SQL statement, 966should be passed **as lists**. For many years, the :func:`_sql.select` 967construct could not participate in this convention smoothly because of the 968very legacy calling pattern where the "WHERE" clause would be passed positionally. 969SQLAlchemy 2.0 finally resolves this by changing the :func:`_sql.select` construct 970to only accept the "generative" style that has for many years been the only 971documented style in the Core tutorial. 972 973Examples of "structural" vs. "data" elements are as follows:: 974 975 # table columns for CREATE TABLE - structural 976 table = Table("table", metadata_obj, Column('x', Integer), Column('y', Integer)) 977 978 # columns in a SELECT statement - structural 979 stmt = select(table.c.x, table.c.y) 980 981 # literal elements in an IN clause - data 982 stmt = stmt.where(table.c.y.in_([1, 2, 3])) 983 984.. seealso:: 985 986 :ref:`change_5284` 987 988 :ref:`error_c9ae` 989 990insert/update/delete DML no longer accept keyword constructor arguments 991----------------------------------------------------------------------- 992 993**Synopsis** 994 995In a similar way as to the previous change to :func:`_sql.select`, the 996constructor arguments to :func:`_sql.insert`, :func:`_sql.update` and 997:func:`_sql.delete` other than the table argument are essentially removed:: 998 999 # no longer supported 1000 stmt = insert(table, values={"x": 10, "y": 15}, inline=True) 1001 1002 # no longer supported 1003 stmt = insert(table, values={"x": 10, "y": 15}, returning=[table.c.x]) 1004 1005 # no longer supported 1006 stmt = table.delete(table.c.x > 15) 1007 1008 # no longer supported 1009 stmt = table.update( 1010 table.c.x < 15, 1011 preserve_parameter_order=True 1012 ).values( 1013 [(table.c.y, 20), (table.c.x, table.c.y + 10)] 1014 ) 1015 1016**Migration to 2.0** 1017 1018The following examples illustrate generative method use for the above 1019examples:: 1020 1021 # use generative methods, **kwargs OK for values() 1022 stmt = insert(table).values(x=10, y=15).inline() 1023 1024 # use generative methods, dictionary also still OK for values() 1025 stmt = insert(table).values({"x": 10, "y": 15}).returning(table.c.x) 1026 1027 # use generative methods 1028 stmt = table.delete().where(table.c.x > 15) 1029 1030 # use generative methods, ordered_values() replaces preserve_parameter_order 1031 stmt = table.update().where( 1032 table.c.x < 15, 1033 ).ordered_values( 1034 (table.c.y, 20), (table.c.x, table.c.y + 10) 1035 ) 1036 1037**Discussion** 1038 1039The API and internals is being simplified for the DML constructs in a similar 1040manner as that of the :func:`_sql.select` construct. 1041 1042 1043 10442.0 Migration - ORM Configuration 1045============================================= 1046 1047Declarative becomes a first class API 1048------------------------------------- 1049 1050**Synopsis** 1051 1052The ``sqlalchemy.ext.declarative`` package is mostly, with some exceptions, 1053moved to the ``sqlalchemy.orm`` package. The :func:`_orm.declarative_base` 1054and :func:`_orm.declared_attr` functions are present without any behavioral 1055changes. A new super-implementation of :func:`_orm.declarative_base` 1056known as :class:`_orm.registry` now serves as the top-level ORM configurational 1057construct, which also provides for decorator-based declarative and new 1058support for classical mappings that integrate with the declarative registry. 1059 1060**Migration to 2.0** 1061 1062Change imports:: 1063 1064 from sqlalchemy.ext import declarative_base, declared_attr 1065 1066To:: 1067 1068 from sqlalchemy.orm import declarative_base, declared_attr 1069 1070**Discussion** 1071 1072After ten years or so of popularity, the ``sqlalchemy.ext.declarative`` 1073package is now integrated into the ``sqlalchemy.orm`` namespace, with the 1074exception of the declarative "extension" classes which remain as Declarative 1075extensions. The change is detailed further in the 1.4 migration guide 1076at :ref:`change_5508`. 1077 1078 1079.. seealso:: 1080 1081 :ref:`orm_mapping_classes_toplevel` - all new unified documentation for 1082 Declarative, classical mapping, dataclasses, attrs, etc. 1083 1084 1085 :ref:`change_5508` 1086 1087 1088The original "mapper()" function now a core element of Declarative, renamed 1089---------------------------------------------------------------------------- 1090 1091**Synopsis** 1092 1093The :func:`_orm.mapper` function moves behind the scenes to be invoked 1094by higher level APIs. The new version of this function is the method 1095:meth:`_orm.registry.map_imperatively` taken from a :class:`_orm.registry` 1096object. 1097 1098**Migration to 2.0** 1099 1100Code that works with classical mappings should change imports and code from:: 1101 1102 from sqlalchemy.orm import mapper 1103 1104 1105 mapper(SomeClass, some_table, properties={ 1106 "related": relationship(SomeRelatedClass) 1107 }) 1108 1109To work from a central :class:`_orm.registry` object:: 1110 1111 from sqlalchemy.orm import registry 1112 1113 mapper_reg = registry() 1114 1115 mapper_reg.map_imperatively(SomeClass, some_table, properties={ 1116 "related": relationship(SomeRelatedClass) 1117 }) 1118 1119The above :class:`_orm.registry` is also the source for declarative mappings, 1120and classical mappings now have access to this registry including string-based 1121configuration on :func:`_orm.relationship`:: 1122 1123 from sqlalchemy.orm import registry 1124 1125 mapper_reg = registry() 1126 1127 Base = mapper_reg.generate_base() 1128 1129 class SomeRelatedClass(Base): 1130 __tablename__ = 'related' 1131 1132 # ... 1133 1134 1135 mapper_reg.map_imperatively(SomeClass, some_table, properties={ 1136 "related": relationship( 1137 "SomeRelatedClass", 1138 primaryjoin="SomeRelatedClass.related_id == SomeClass.id" 1139 ) 1140 }) 1141 1142 1143**Discussion** 1144 1145By popular demand, "classical mapping" is staying around, however the new 1146form of it is based off of the :class:`_orm.registry` object and is available 1147as :meth:`_orm.registry.map_imperatively`. 1148 1149In addition, the primary rationale used for "classical mapping" is that of 1150keeping the :class:`_schema.Table` setup distinct from the class. Declarative 1151has always allowed this style using so-called 1152:ref:`hybrid declarative <orm_imperative_table_configuration>`. However, to 1153remove the base class requirement, a first class :ref:`decorator 1154<declarative_config_toplevel>` form has been added. 1155 1156As yet another separate but related enhancement, support for :ref:`Python 1157dataclasses <orm_declarative_dataclasses>` is added as well to both 1158declarative decorator and classical mapping forms. 1159 1160.. seealso:: 1161 1162 :ref:`orm_mapping_classes_toplevel` - all new unified documentation for 1163 Declarative, classical mapping, dataclasses, attrs, etc. 1164 11652.0 Migration - ORM Usage 1166============================================= 1167 1168The biggest visible change in SQLAlchemy 2.0 is the use of 1169:meth:`_orm.Session.execute` in conjunction with :func:`_sql.select` to run ORM 1170queries, instead of using :meth:`_orm.Session.query`. As mentioned elsewhere, 1171there is no plan to actually remove the :meth:`_orm.Session.query` API itself, 1172as it is now implemented by using the new API internally it will remain as a 1173legacy API, and both APIs can be used freely. 1174 1175The table below provides an introduction to the general change in 1176calling form with links to documentation for each technique 1177presented. The individual migration notes are in the embedded sections 1178following the table, and may include additional notes not summarized here. 1179 1180 1181.. container:: sliding-table 1182 1183 .. list-table:: **Overview of Major ORM Querying Patterns** 1184 :header-rows: 1 1185 1186 * - :term:`1.x style` form 1187 - :term:`2.0 style` form 1188 - See Also 1189 1190 * - :: 1191 1192 session.query(User).get(42) 1193 1194 - :: 1195 1196 session.get(User, 42) 1197 1198 - :ref:`migration_20_get_to_session` 1199 1200 * - :: 1201 1202 session.query(User).all() 1203 1204 - :: 1205 1206 session.execute( 1207 select(User) 1208 ).scalars().all() 1209 1210 - :ref:`migration_20_unify_select` 1211 1212 :meth:`_engine.Result.scalars` 1213 1214 * - :: 1215 1216 session.query(User).\ 1217 filter_by(name='some user').one() 1218 1219 - :: 1220 1221 session.execute( 1222 select(User). 1223 filter_by(name="some user") 1224 ).scalar_one() 1225 1226 - :ref:`migration_20_unify_select` 1227 1228 :meth:`_engine.Result.scalar_one` 1229 1230 * - :: 1231 1232 session.query(User).\ 1233 filter_by(name='some user').first() 1234 1235 1236 - :: 1237 1238 session.execute( 1239 select(User). 1240 filter_by(name="some user"). 1241 limit(1) 1242 ).scalars().first() 1243 1244 - :ref:`migration_20_unify_select` 1245 1246 :meth:`_engine.Result.first` 1247 1248 * - :: 1249 1250 session.query(User).options( 1251 joinedload(User.addresses) 1252 ).all() 1253 1254 - :: 1255 1256 session.execute( 1257 select(User). 1258 options( 1259 joinedload(User.addresses) 1260 ) 1261 ).unique().all() 1262 1263 - :ref:`joinedload_not_uniqued` 1264 1265 * - :: 1266 1267 session.query(User).\ 1268 join(Address).\ 1269 filter(Address.email == 'e@sa.us').\ 1270 all() 1271 1272 - :: 1273 1274 session.execute( 1275 select(User). 1276 join(Address). 1277 where(Address.email == 'e@sa.us') 1278 ).scalars().all() 1279 1280 - :ref:`migration_20_unify_select` 1281 1282 :ref:`orm_queryguide_joins` 1283 1284 * - :: 1285 1286 session.query(User).from_statement( 1287 text("select * from users") 1288 ).all() 1289 1290 - :: 1291 1292 session.execute( 1293 select(User). 1294 from_statement( 1295 text("select * from users") 1296 ) 1297 ).scalars().all() 1298 1299 - :ref:`orm_queryguide_selecting_text` 1300 1301 * - :: 1302 1303 session.query(User).\ 1304 join(User.addresses).\ 1305 options( 1306 contains_eager(User.addresses) 1307 ).\ 1308 populate_existing().all() 1309 1310 - :: 1311 1312 session.execute( 1313 select(User). 1314 join(User.addresses). 1315 options(contains_eager(User.addresses)). 1316 execution_options(populate_existing=True) 1317 ).scalars().all() 1318 1319 - 1320 1321 :ref:`orm_queryguide_execution_options` 1322 1323 :ref:`orm_queryguide_populate_existing` 1324 1325 * 1326 - :: 1327 1328 session.query(User).\ 1329 filter(User.name == 'foo').\ 1330 update( 1331 {"fullname": "Foo Bar"}, 1332 synchronize_session="evaluate" 1333 ) 1334 1335 1336 - :: 1337 1338 session.execute( 1339 update(User). 1340 where(User.name == 'foo'). 1341 values(fullname="Foo Bar"). 1342 execution_options(synchronize_session="evaluate") 1343 ) 1344 1345 - :ref:`orm_expression_update_delete` 1346 1347 * 1348 - :: 1349 1350 session.query(User).count() 1351 1352 - :: 1353 1354 session.scalar(select(func.count()).select_from(User)) 1355 session.scalar(select(func.count(User.id))) 1356 1357 - :meth:`_orm.Session.scalar` 1358 1359.. _migration_20_unify_select: 1360 1361ORM Query Unified with Core Select 1362---------------------------------- 1363 1364**Synopsis** 1365 1366The :class:`_orm.Query` object (as well as the :class:`_baked.BakedQuery` and 1367:class:`_horizontal.ShardedQuery` extensions) become long term legacy objects, 1368replaced by the direct usage of the :func:`_sql.select` construct in conjunction 1369with the :meth:`_orm.Session.execute` method. Results 1370that are returned from :class:`_orm.Query` in the form of lists of objects 1371or tuples, or as scalar ORM objects are returned from :meth:`_orm.Session.execute` 1372uniformly as :class:`_engine.Result` objects, which feature an interface 1373consistent with that of Core execution. 1374 1375Legacy code examples are illustrated below:: 1376 1377 session = Session(engine) 1378 1379 # becomes legacy use case 1380 user = session.query(User).filter_by(name='some user').one() 1381 1382 # becomes legacy use case 1383 user = session.query(User).filter_by(name='some user').first() 1384 1385 # becomes legacy use case 1386 user = session.query(User).get(5) 1387 1388 # becomes legacy use case 1389 for user in session.query(User).join(User.addresses).filter(Address.email == 'some@email.com'): 1390 # ... 1391 1392 # becomes legacy use case 1393 users = session.query(User).options(joinedload(User.addresses)).order_by(User.id).all() 1394 1395 # becomes legacy use case 1396 users = session.query(User).from_statement( 1397 text("select * from users") 1398 ).all() 1399 1400 # etc 1401 1402**Migration to 2.0** 1403 1404Because the vast majority of an ORM application is expected to make use of 1405:class:`_orm.Query` objects as well as that the :class:`_orm.Query` interface 1406being available does not impact the new interface, the object will stay 1407around in 2.0 but will no longer be part of documentation nor will it be 1408supported for the most part. The :func:`_sql.select` construct now suits 1409both the Core and ORM use cases, which when invoked via the :meth:`_orm.Session.execute` 1410method will return ORM-oriented results, that is, ORM objects if that's what 1411was requested. 1412 1413The :func:`_sql.Select` construct **adds many new methods** for 1414compatibility with :class:`_orm.Query`, including :meth:`_sql.Select.filter` 1415:meth:`_sql.Select.filter_by`, newly reworked :meth:`_sql.Select.join` 1416and :meth:`_sql.Select.outerjoin` methods, :meth:`_sql.Select.options`, 1417etc. Other more supplemental methods of :class:`_orm.Query` such as 1418:meth:`_orm.Query.populate_existing` are implemented via execution options. 1419 1420Return results are in terms of a 1421:class:`_result.Result` object, the new version of the SQLAlchemy 1422``ResultProxy`` object, which also adds many new methods for compatibility 1423with :class:`_orm.Query`, including :meth:`_engine.Result.one`, :meth:`_engine.Result.all`, 1424:meth:`_engine.Result.first`, :meth:`_engine.Result.one_or_none`, etc. 1425 1426The :class:`_engine.Result` object however does require some different calling 1427patterns, in that when first returned it will **always return tuples** 1428and it will **not deduplicate results in memory**. In order to return 1429single ORM objects the way :class:`_orm.Query` does, the :meth:`_engine.Result.scalars` 1430modifier must be called first. In order to return uniqued objects, as is 1431necessary when using joined eager loading, the :meth:`_engine.Result.unique` 1432modifier must be called first. 1433 1434Documentation for all new features of :func:`_sql.select` including execution 1435options, etc. are at :doc:`/orm/queryguide`. 1436 1437Below are some examples of how to migrate to :func:`_sql.select`:: 1438 1439 1440 session = Session(engine) 1441 1442 user = session.execute( 1443 select(User).filter_by(name="some user") 1444 ).scalar_one() 1445 1446 # for first(), no LIMIT is applied automatically; add limit(1) if LIMIT 1447 # is desired on the query 1448 user = session.execute( 1449 select(User).filter_by(name="some user").limit(1) 1450 ).scalars().first() 1451 1452 # get() moves to the Session directly 1453 user = session.get(User, 5) 1454 1455 for user in session.execute( 1456 select(User).join(User.addresses).filter(Address.email == "some@email.case") 1457 ).scalars(): 1458 # ... 1459 1460 # when using joinedload() against collections, use unique() on the result 1461 users = session.execute( 1462 select(User).options(joinedload(User.addresses)).order_by(User.id) 1463 ).unique().all() 1464 1465 # select() has ORM-ish methods like from_statement() that only work 1466 # if the statement is against ORM entities 1467 users = session.execute( 1468 select(User).from_statement(text("select * from users")) 1469 ).scalars().all() 1470 1471**Discussion** 1472 1473The fact that SQLAlchemy has both a :func:`_expression.select` construct 1474as well as a separate :class:`_orm.Query` object that features an extremely 1475similar, but fundamentally incompatible interface is likely the greatest 1476inconsistency in SQLAlchemy, one that arose as a result of small incremental 1477additions over time that added up to two major APIs that are divergent. 1478 1479In SQLAlchemy's first releases, the :class:`_orm.Query` object didn't exist 1480at all. The original idea was that the :class:`_orm.Mapper` construct itself would 1481be able to select rows, and that :class:`_schema.Table` objects, not classes, 1482would be used to create the various criteria in a Core-style approach. The 1483:class:`_query.Query` came along some months / years into SQLAlchemy's history 1484as a user proposal for a new, "buildable" querying object originally called ``SelectResults`` 1485was accepted. 1486Concepts like a ``.where()`` method, which ``SelectResults`` called ``.filter()``, 1487were not present in SQLAlchemy previously, and the :func:`_sql.select` construct 1488used only the "all-at-once" construction style that's now deprecated 1489at :ref:`migration_20_5284`. 1490 1491As the new approach took off, the object evolved into the :class:`_orm.Query` 1492object as new features such as being able to select individual columns, 1493being able to select multiple entities at once, being able to build subqueries 1494from a :class:`_orm.Query` object rather than from a :class:`_sql.select` 1495object were added. The goal became that :class:`_orm.Query` should have the 1496full functionality of :class:`_sql.select` in that it could be composed to 1497build SELECT statements fully with no explicit use of :func:`_sql.select` 1498needed. At the same time, :func:`_sql.select` had also evolved "generative" 1499methods like :meth:`_sql.Select.where` and :meth:`_sql.Select.order_by`. 1500 1501In modern SQLAlchemy, this goal has been achieved and the two objects are now 1502completely overlapping in functionality. The major challenge to unifying these 1503objects was that the :func:`_sql.select` object needed to remain **completely 1504agnostic of the ORM**. To achieve this, the vast majority of logic from 1505:class:`_orm.Query` has been moved into the SQL compile phase, where 1506ORM-specific compiler plugins receive the 1507:class:`_sql.Select` construct and interpret its contents in terms of an 1508ORM-style query, before passing off to the core-level compiler in order to 1509create a SQL string. With the advent of the new 1510`SQL compilation caching system <change_4639>`, 1511the majority of this ORM logic is also cached. 1512 1513 1514.. seealso:: 1515 1516 :ref:`change_5159` 1517 1518.. _migration_20_get_to_session: 1519 1520ORM Query - get() method moves to Session 1521------------------------------------------ 1522 1523**Synopsis** 1524 1525The :meth:`_orm.Query.get` method remains for legacy purposes, but the 1526primary interface is now the :meth:`_orm.Session.get` method:: 1527 1528 # legacy usage 1529 user_obj = session.query(User).get(5) 1530 1531**Migration to 2.0** 1532 1533In 1.4 / 2.0, the :class:`_orm.Session` object adds a new 1534:meth:`_orm.Session.get` method:: 1535 1536 # 1.4 / 2.0 cross-compatible use 1537 user_obj = session.get(User, 5) 1538 1539**Discussion** 1540 1541The :class:`_orm.Query` object is to be a legacy object in 2.0, as ORM 1542queries are now available using the :func:`_sql.select` object. As the 1543:meth:`_orm.Query.get` method defines a special interaction with the 1544:class:`_orm.Session` and does not necessarily even emit a query, it's more 1545appropriate that it be part of :class:`_orm.Session`, where it is similar 1546to other "identity" methods such as :class:`_orm.Session.refresh` and 1547:class:`_orm.Session.merge`. 1548 1549SQLAlchemy originally included "get()" to resemble the Hibernate 1550``Session.load()`` method. As is so often the case, we got it slightly 1551wrong as this method is really more about the :class:`_orm.Session` than 1552with writing a SQL query. 1553 1554.. _migration_20_orm_query_join_strings: 1555 1556ORM Query - Joining / loading on relationships uses attributes, not strings 1557---------------------------------------------------------------------------- 1558 1559**Synopsis** 1560 1561This refers to patterns such as that of :meth:`_query.Query.join` as well as 1562query options like :func:`_orm.joinedload` which currently accept a mixture of 1563string attribute names or actual class attributes. The string forms 1564will all be removed in 2.0:: 1565 1566 # string use removed 1567 q = session.query(User).join("addresses") 1568 1569 # string use removed 1570 q = session.query(User).options(joinedload("addresses")) 1571 1572 # string use removed 1573 q = session.query(Address).filter(with_parent(u1, "addresses")) 1574 1575 1576**Migration to 2.0** 1577 1578Modern SQLAlchemy 1.x versions support the recommended technique which 1579is to use mapped attributes:: 1580 1581 # compatible with all modern SQLAlchemy versions 1582 1583 q = session.query(User).join(User.addresses) 1584 1585 q = session.query(User).options(joinedload(User.addresses)) 1586 1587 q = session.query(Address).filter(with_parent(u1, User.addresses)) 1588 1589The same techniques apply to :term:`2.0-style` style use:: 1590 1591 # SQLAlchemy 1.4 / 2.0 cross compatible use 1592 1593 stmt = select(User).join(User.addresses) 1594 result = session.execute(stmt) 1595 1596 stmt = select(User).options(joinedload(User.addresses)) 1597 result = session.execute(stmt) 1598 1599 stmt = select(Address).where(with_parent(u1, User.addresses)) 1600 result = session.execute(stmt) 1601 1602**Discussion** 1603 1604The string calling form is ambiguous and requires that the internals do extra 1605work to determine the appropriate path and retrieve the correct mapped 1606property. By passing the ORM mapped attribute directly, not only is the 1607necessary information passed up front, the attribute is also typed and is 1608more potentially compatible with IDEs and pep-484 integrations. 1609 1610 1611ORM Query - Chaining using lists of attributes, rather than individual calls, removed 1612------------------------------------------------------------------------------------- 1613 1614**Synopsis** 1615 1616"Chained" forms of joining and loader options which accept multiple mapped 1617attributes in a list will be removed:: 1618 1619 # chaining removed 1620 q = session.query(User).join("orders", "items", "keywords") 1621 1622 1623**Migration to 2.0** 1624 1625Use individual calls to :meth:`_orm.Query.join` for 1.x /2.0 cross compatible 1626use:: 1627 1628 q = session.query(User).join(User.orders).join(Order.items).join(Item.keywords) 1629 1630For :term:`2.0-style` use, :class:`_sql.Select` has the same behavior of 1631:meth:`_sql.Select.join`, and also features a new :meth:`_sql.Select.join_from` 1632method that allows an explicit left side:: 1633 1634 # 1.4 / 2.0 cross compatible 1635 1636 stmt = select(User).join(User.orders).join(Order.items).join(Item.keywords) 1637 result = session.execute(stmt) 1638 1639 # join_from can also be helpful 1640 stmt = select(User).join_from(User, Order).join_from(Order, Item, Order.items) 1641 result = session.execute(stmt) 1642 1643**Discussion** 1644 1645Removing the chaining of attributes is in line with simplifying the calling 1646interface of methods such as :meth:`_sql.Select.join`. 1647 1648.. _migration_20_query_join_options: 1649 1650ORM Query - join(..., aliased=True), from_joinpoint removed 1651----------------------------------------------------------- 1652 1653**Synopsis** 1654 1655The ``aliased=True`` option on :meth:`_query.Query.join` is removed, as is 1656the ``from_joinpoint`` flag:: 1657 1658 # no longer supported 1659 q = session.query(Node).\ 1660 join("children", aliased=True).filter(Node.name == "some sub child"). 1661 join("children", from_joinpoint=True, aliased=True).\ 1662 filter(Node.name == 'some sub sub child') 1663 1664**Migration to 2.0** 1665 1666Use explicit aliases instead:: 1667 1668 n1 = aliased(Node) 1669 n2 = aliased(Node) 1670 1671 q = select(Node).join(Node.children.of_type(n1)).\ 1672 where(n1.name == "some sub child").\ 1673 join(n1.children.of_type(n2)).\ 1674 where(n2.name == "some sub child") 1675 1676 1677**Discussion** 1678 1679The ``aliased=True`` option on :meth:`_query.Query.join` is another feature that 1680seems to be almost never used, based on extensive code searches to find 1681actual use of this feature. The internal complexity that the ``aliased=True`` 1682flag requires is **enormous**, and will be going away in 2.0. 1683 1684Most users aren't familiar with this flag, however it allows for automatic 1685aliasing of elements along a join, which then applies automatic aliasing 1686to filter conditions. The original use case was to assist in long chains 1687of self-referential joins, as in the example shown above. However, 1688the automatic adaption of the filter criteria is enormously 1689complicated internally and almost never used in real world applications. The 1690pattern also leads to issues such as if filter criteria need to be added 1691at each link in the chain; the pattern then must use the ``from_joinpoint`` 1692flag which SQLAlchemy developers could absolutely find no occurrence of this 1693parameter ever being used in real world applications. 1694 1695The ``aliased=True`` and ``from_joinpoint`` parameters were developed at a time 1696when the :class:`_query.Query` object didn't yet have good capabilities regarding 1697joining along relationship attributes, functions like 1698:meth:`.PropComparator.of_type` did not exist, and the :func:`.aliased` 1699construct itself didn't exist early on. 1700 1701.. _migration_20_query_distinct: 1702 1703Using DISTINCT with additional columns, but only select the entity 1704------------------------------------------------------------------- 1705 1706**Synopsis** 1707 1708:class:`_query.Query` will automatically add columns in the ORDER BY when 1709distinct is used. The following query will select from all User columns 1710as well as "address.email_address" but only return User objects:: 1711 1712 # 1.xx code 1713 1714 result = session.query(User).join(User.addresses).\ 1715 distinct().order_by(Address.email_address).all() 1716 1717In version 2.0, the "email_address" column will not be automatically added 1718to the columns clause, and the above query will fail, since relational 1719databases won't allow you to ORDER BY "address.email_address" when using 1720DISTINCT if it isn't also in the columns clause. 1721 1722**Migration to 2.0** 1723 1724In 2.0, the column must be added explicitly. To resolve the issue of only 1725returning the main entity object, and not the extra column, use the 1726:meth:`_result.Result.columns` method:: 1727 1728 # 1.4 / 2.0 code 1729 1730 stmt = select(User, Address.email_address).join(User.addresses).\ 1731 distinct().order_by(Address.email_address) 1732 1733 result = session.execute(stmt).columns(User).all() 1734 1735**Discussion** 1736 1737This case is an example of the limited flexibility of :class:`_orm.Query` 1738leading to the case where implicit, "magical" behavior needed to be added; 1739the "email_address" column is implicitly added to the columns clause, then 1740additional internal logic would omit that column from the actual results 1741returned. 1742 1743The new approach simplifies the interaction and makes what's going on 1744explicit, while still making it possible to fulfill the original use case 1745without inconvenience. 1746 1747 1748.. _migration_20_query_from_self: 1749 1750Selecting from the query itself as a subquery, e.g. "from_self()" 1751------------------------------------------------------------------- 1752 1753**Synopsis** 1754 1755The :meth:`_orm.Query.from_self` method will be removed from :class:`_orm.Query`:: 1756 1757 # from_self is removed 1758 q = session.query(User, Address.email_address).\ 1759 join(User.addresses).\ 1760 from_self(User).order_by(Address.email_address) 1761 1762 1763**Migration to 2.0** 1764 1765The :func:`._orm.aliased` construct may be used to emit ORM queries against 1766an entity that is in terms of any arbitrary selectable. It has been enhanced 1767in version 1.4 to smoothly accommodate being used multiple times against 1768the same subquery for different entities as well. This can be 1769used in :term:`1.x style` with :class:`_orm.Query` as below; note that 1770since the final query wants to query in terms of both the ``User`` and 1771``Address`` entities, two separate :func:`_orm.aliased` constructs are created:: 1772 1773 from sqlalchemy.orm import aliased 1774 1775 subq = session.query(User, Address.email_address).\ 1776 join(User.addresses).subquery() 1777 1778 ua = aliased(User, subq) 1779 1780 aa = aliased(Address, subq) 1781 1782 q = session.query(ua, aa).order_by(aa.email_address) 1783 1784The same form may be used in :term:`2.0 style`:: 1785 1786 from sqlalchemy.orm import aliased 1787 1788 subq = select(User, Address.email_address).\ 1789 join(User.addresses).subquery() 1790 1791 ua = aliased(User, subq) 1792 1793 aa = aliased(Address, subq) 1794 1795 stmt = select(ua, aa).order_by(aa.email_address) 1796 1797 result = session.execute(stmt) 1798 1799 1800**Discussion** 1801 1802The :meth:`_query.Query.from_self` method is a very complicated method that is rarely 1803used. The purpose of this method is to convert a :class:`_query.Query` into a 1804subquery, then return a new :class:`_query.Query` which SELECTs from that subquery. 1805The elaborate aspect of this method is that the returned query applies 1806automatic translation of ORM entities and columns to be stated in the SELECT in 1807terms of the subquery, as well as that it allows the entities and columns to be 1808SELECTed from to be modified. 1809 1810Because :meth:`_query.Query.from_self` packs an intense amount of implicit 1811translation into the SQL it produces, while it does allow a certain kind of 1812pattern to be executed very succinctly, real world use of this method is 1813infrequent as it is not simple to understand. 1814 1815The new approach makes use of the :func:`_orm.aliased` construct so that the 1816ORM internals don't need to guess which entities and columns should be adapted 1817and in what way; in the example above, the ``ua`` and ``aa`` objects, both 1818of which are :class:`_orm.AliasedClass` instances, provide to the internals 1819an unambiguous marker as to where the subquery should be referred towards 1820as well as what entity column or relationship is being considered for a given 1821component of the query. 1822 1823SQLAlchemy 1.4 also features an improved labeling style that no longer requires 1824the use of long labels that include the table name in order to disambiguate 1825columns of same names from different tables. In the above examples, even if 1826our ``User`` and ``Address`` entities have overlapping column names, we can 1827select from both entities at once without having to specify any particular 1828labeling:: 1829 1830 # 1.4 / 2.0 code 1831 1832 subq = select(User, Address).\ 1833 join(User.addresses).subquery() 1834 1835 ua = aliased(User, subq) 1836 aa = aliased(Address, subq) 1837 1838 stmt = select(ua, aa).order_by(aa.email_address) 1839 result = session.execute(stmt) 1840 1841The above query will disambiguate the ``.id`` column of ``User`` and 1842``Address``, where ``Address.id`` is rendered and tracked as ``id_1``:: 1843 1844 SELECT anon_1.id AS anon_1_id, anon_1.id_1 AS anon_1_id_1, 1845 anon_1.user_id AS anon_1_user_id, 1846 anon_1.email_address AS anon_1_email_address 1847 FROM ( 1848 SELECT "user".id AS id, address.id AS id_1, 1849 address.user_id AS user_id, address.email_address AS email_address 1850 FROM "user" JOIN address ON "user".id = address.user_id 1851 ) AS anon_1 ORDER BY anon_1.email_address 1852 1853 1854:ticket:`5221` 1855 1856Selecting entities from alternative selectables; Query.select_entity_from() 1857--------------------------------------------------------------------------- 1858 1859**Synopsis** 1860 1861The :meth:`_orm.Query.select_entity_from` method will be removed in 2.0:: 1862 1863 subquery = session.query(User).filter(User.id == 5).subquery() 1864 1865 user = session.query(User).select_entity_from(subquery).first() 1866 1867**Migration to 2.0** 1868 1869As is the case described at :ref:`migration_20_query_from_self`, the 1870:func:`_orm.aliased` object provides a single place that operations like 1871"select entity from a subquery" may be achieved. Using :term:`1.x style`:: 1872 1873 from sqlalchemy.orm import aliased 1874 1875 subquery = session.query(User).filter(User.name.like("%somename%")).subquery() 1876 1877 ua = aliased(User, subquery) 1878 1879 user = session.query(ua).order_by(ua.id).first() 1880 1881Using :term:`2.0 style`:: 1882 1883 from sqlalchemy.orm import aliased 1884 1885 subquery = select(User).where(User.name.like("%somename%")).subquery() 1886 1887 ua = aliased(User, subquery) 1888 1889 # note that LIMIT 1 is not automatically supplied, if needed 1890 user = session.execute(select(ua).order_by(ua.id).limit(1)).scalars().first() 1891 1892**Discussion** 1893 1894The points here are basically the same as those discussed at 1895:ref:`migration_20_query_from_self`. The :meth:`_orm.Query.select_from_entity` 1896method was another way to instruct the query to load rows for a particular 1897ORM mapped entity from an alternate selectable, which involved having the 1898ORM apply automatic aliasing to that entity wherever it was used in the 1899query later on, such as in the WHERE clause or ORDER BY. This intensely 1900complex feature is seldom used in this way, where as was the case with 1901:meth:`_orm.Query.from_self`, it's much easier to follow what's going on 1902when using an explicit :func:`_orm.aliased` object, both from a user point 1903of view as well as how the internals of the SQLAlchemy ORM must handle it. 1904 1905 1906.. _joinedload_not_uniqued: 1907 1908ORM Rows not uniquified by default 1909---------------------------------- 1910 1911**Synopsis** 1912 1913ORM rows returned by ``session.execute(stmt)`` are no longer automatically 1914"uniqued". This will normally be a welcome change, except in the case 1915where the "joined eager loading" loader strategy is used with collections:: 1916 1917 # In the legacy API, many rows each have the same User primary key, but 1918 # only one User per primary key is returned 1919 users = session.query(User).options(joinedload(User.addresses)) 1920 1921 # In the new API, uniquing is available but not implicitly 1922 # enabled 1923 result = session.execute( 1924 select(User).options(joinedload(User.addresses)) 1925 ) 1926 1927 # this actually will raise an error to let the user know that 1928 # uniquing should be applied 1929 rows = result.all() 1930 1931**Migrating to 2.0** 1932 1933When using a joined load of a collection, it's required that the 1934:meth:`_engine.Result.unique` method is called. The ORM will actually set 1935a default row handler that will raise an error if this is not done, to 1936ensure that a joined eager load collection does not return duplicate rows 1937while still maintaining explicitness:: 1938 1939 # 1.4 / 2.0 code 1940 1941 stmt = select(User).options(joinedload(User.addresses)) 1942 1943 # statement will raise if unique() is not used, due to joinedload() 1944 # of a collection. in all other cases, unique() is not needed. 1945 # By stating unique() explicitly, confusion over discrepancies between 1946 # number of objects/ rows returned vs. "SELECT COUNT(*)" is resolved 1947 rows = session.execute(stmt).unique().all() 1948 1949**Discussion** 1950 1951The situation here is a little bit unusual, in that SQLAlchemy is requiring 1952that a method be invoked that it is in fact entirely capable of doing 1953automatically. The reason for requiring that the method be called is to 1954ensure the developer is "opting in" to the use of the 1955:meth:`_engine.Result.unique` method, such that they will not be confused when 1956a straight count of rows does not conflict with the count of 1957records in the actual result set, which has been a long running source of 1958user confusion and bug reports for many years. That the uniquifying is 1959not happening in any other case by default will improve performance and 1960also improve clarity in those cases where automatic uniquing was causing 1961confusing results. 1962 1963To the degree that having to call :meth:`_engine.Result.unique` when joined 1964eager load collections are used is inconvenient, in modern SQLAlchemy 1965the :func:`_orm.selectinload` strategy presents a collection-oriented 1966eager loader that is superior in most respects to :func:`_orm.joinedload` 1967and should be preferred. 1968 1969 1970Autocommit mode removed from Session; autobegin support added 1971------------------------------------------------------------- 1972 1973**Synopsis** 1974 1975The :class:`_orm.Session` will no longer support "autocommit" mode, that 1976is, this pattern:: 1977 1978 from sqlalchemy.orm import Session 1979 1980 sess = Session(engine, autocommit=True) 1981 1982 # no transaction begun, but emits SQL, won't be supported 1983 obj = sess.query(Class).first() 1984 1985 1986 # session flushes in a transaction that it begins and 1987 # commits, won't be supported 1988 sess.flush() 1989 1990 1991**Migration to 2.0** 1992 1993The main reason a :class:`_orm.Session` is used in "autocommit" mode 1994is so that the :meth:`_orm.Session.begin` method is available, so that framework 1995integrations and event hooks can control when this event happens. In 1.4, 1996the :class:`_orm.Session` now features :ref:`autobegin behavior <change_5074>` 1997which resolves this issue; the :meth:`_orm.Session.begin` method may now 1998be called:: 1999 2000 2001 from sqlalchemy.orm import Session 2002 2003 sess = Session(engine) 2004 2005 sess.begin() # begin explicitly; if not called, will autobegin 2006 # when database access is needed 2007 2008 sess.add(obj) 2009 2010 sess.commit() 2011 2012**Discussion** 2013 2014The "autocommit" mode is another holdover from the first versions 2015of SQLAlchemy. The flag has stayed around mostly in support of allowing 2016explicit use of :meth:`_orm.Session.begin`, which is now solved by 1.4, 2017as well as to allow the use of "subtransactions", which are also removed in 20182.0. 2019 2020Session "subtransaction" behavior removed 2021------------------------------------------ 2022 2023See the section :ref:`session_subtransactions` for background on this 2024change. 2025 2026 20272.0 Migration - ORM Extension and Recipe Changes 2028================================================ 2029 2030Dogpile cache recipe and Horizontal Sharding uses new Session API 2031------------------------------------------------------------------ 2032 2033As the :class:`_orm.Query` object becomes legacy, these two recipes 2034which previously relied upon subclassing of the :class:`_orm.Query` 2035object now make use of the :meth:`_orm.SessionEvents.do_orm_execute` 2036hook. See the section :ref:`do_orm_execute_re_executing` for 2037an example. 2038 2039 2040 2041Baked Query Extension Superseded by built-in caching 2042----------------------------------------------------- 2043 2044The baked query extension is superseded by the built in caching system and 2045is no longer used by the ORM internals. 2046 2047See :ref:`sql_caching` for full background on the new caching system. 2048 2049 2050 2051Asyncio Support 2052===================== 2053 2054SQLAlchemy 1.4 includes asyncio support for both Core and ORM. 2055The new API exclusively makes use of the "future" patterns noted above. 2056See :ref:`change_3414` for background. 2057