From: Federico Caselli Date: Tue, 3 Nov 2020 22:24:28 +0000 (+0100) Subject: Some small improvements on the tutorial 2.0 documents X-Git-Tag: rel_1_4_0b2~152^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f18316a14f3858acfd3e813753c2c69821a27d57;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git Some small improvements on the tutorial 2.0 documents Change-Id: I7fb37d45c29307b2213bebd0ef280d73804ac473 --- diff --git a/doc/build/core/operators.rst b/doc/build/core/operators.rst index 5972b53d5f..ac9633b776 100644 --- a/doc/build/core/operators.rst +++ b/doc/build/core/operators.rst @@ -89,8 +89,8 @@ available on descendants of these classes, including: * :class:`_sql.ColumnElement` objects more generally, which are the root of all Core SQL Expression language column-level expressions -* :class:`_orm.InstrumentedAttribute` objects, which are ORM level mapped - attributes. +* :class:`_orm.InstrumentedAttribute` objects, which are ORM + level mapped attributes. The operators are first introduced in the tutorial sections, including: diff --git a/doc/build/glossary.rst b/doc/build/glossary.rst index d47f0397a0..f5ebe0a7da 100644 --- a/doc/build/glossary.rst +++ b/doc/build/glossary.rst @@ -1222,7 +1222,7 @@ Glossary cursor A control structure that enables traversal over the records in a database. - In the Python DBAPI, the cursor object in fact the starting point + In the Python DBAPI, the cursor object is in fact the starting point for statement execution as well as the interface used for fetching results. diff --git a/doc/build/orm/extensions/asyncio.rst b/doc/build/orm/extensions/asyncio.rst index 2afda1c102..a22529ec5e 100644 --- a/doc/build/orm/extensions/asyncio.rst +++ b/doc/build/orm/extensions/asyncio.rst @@ -1,7 +1,7 @@ .. _asyncio_toplevel: -asyncio -======= +Asynchronous I/O (asyncio) +========================== Support for Python asyncio. Support for Core and ORM usage is included, using asyncio-compatible dialects. diff --git a/doc/build/orm/internals.rst b/doc/build/orm/internals.rst index 3fdfe500ee..8f26f7c3c0 100644 --- a/doc/build/orm/internals.rst +++ b/doc/build/orm/internals.rst @@ -8,17 +8,17 @@ sections, are listed here. .. currentmodule:: sqlalchemy.orm -.. autoclass:: sqlalchemy.orm.state.AttributeState +.. autoclass:: AttributeState :members: -.. autoclass:: sqlalchemy.orm.util.CascadeOptions +.. autoclass:: CascadeOptions :members: -.. autoclass:: sqlalchemy.orm.instrumentation.ClassManager +.. autoclass:: ClassManager :members: :inherited-members: -.. autoclass:: sqlalchemy.orm.ColumnProperty +.. autoclass:: ColumnProperty :members: .. attribute:: Comparator.expressions @@ -32,35 +32,35 @@ sections, are listed here. :ref:`maptojoin` - usage example -.. autoclass:: sqlalchemy.orm.CompositeProperty +.. autoclass:: CompositeProperty :members: -.. autoclass:: sqlalchemy.orm.attributes.Event +.. autoclass:: AttributeEvent :members: -.. autoclass:: sqlalchemy.orm.identity.IdentityMap +.. autoclass:: IdentityMap :members: -.. autoclass:: sqlalchemy.orm.base.InspectionAttr +.. autoclass:: InspectionAttr :members: -.. autoclass:: sqlalchemy.orm.base.InspectionAttrInfo +.. autoclass:: InspectionAttrInfo :members: -.. autoclass:: sqlalchemy.orm.state.InstanceState +.. autoclass:: InstanceState :members: -.. autoclass:: sqlalchemy.orm.attributes.InstrumentedAttribute +.. autoclass:: InstrumentedAttribute :members: __get__, __set__, __delete__ :undoc-members: -.. autodata:: sqlalchemy.orm.MANYTOONE +.. autodata:: MANYTOONE -.. autodata:: sqlalchemy.orm.MANYTOMANY +.. autodata:: MANYTOMANY -.. autoclass:: sqlalchemy.orm.MapperProperty +.. autoclass:: MapperProperty :members: .. py:attribute:: info @@ -83,35 +83,35 @@ sections, are listed here. :attr:`.SchemaItem.info` -.. autodata:: sqlalchemy.orm.interfaces.NOT_EXTENSION +.. autodata:: NOT_EXTENSION -.. autofunction:: sqlalchemy.orm.loading.merge_result +.. autofunction:: merge_result -.. autofunction:: sqlalchemy.orm.loading.merge_frozen_result +.. autofunction:: merge_frozen_result -.. autodata:: sqlalchemy.orm.ONETOMANY +.. autodata:: ONETOMANY -.. autoclass:: sqlalchemy.orm.PropComparator +.. autoclass:: PropComparator :members: :inherited-members: -.. autoclass:: sqlalchemy.orm.RelationshipProperty +.. autoclass:: RelationshipProperty :members: :inherited-members: -.. autoclass:: sqlalchemy.orm.SynonymProperty +.. autoclass:: SynonymProperty :members: :inherited-members: -.. autoclass:: sqlalchemy.orm.query.QueryContext +.. autoclass:: QueryContext :members: -.. autoclass:: sqlalchemy.orm.attributes.QueryableAttribute +.. autoclass:: QueryableAttribute :members: :inherited-members: -.. autoclass:: sqlalchemy.orm.session.UOWTransaction +.. autoclass:: UOWTransaction :members: diff --git a/doc/build/orm/queryguide.rst b/doc/build/orm/queryguide.rst index 5576882a6c..1ae44e23c6 100644 --- a/doc/build/orm/queryguide.rst +++ b/doc/build/orm/queryguide.rst @@ -132,7 +132,6 @@ To invoke a :class:`_sql.Select` with the ORM, it is passed to spongebob Spongebob Squarepants - .. _orm_queryguide_select_columns: Selecting ORM Entities and Attributes @@ -145,7 +144,10 @@ are converted into ORM-annotated :class:`_sql.FromClause` and A :class:`_sql.Select` object that contains ORM-annotated entities is normally executed using a :class:`_orm.Session` object, and not a :class:`_future.Connection` -object, so that ORM-related features may take effect. +object, so that ORM-related features may take effect, including that +instances of ORM-mapped objects may be returned. When using the +:class:`_future.Connection` directly, result rows will only contain +column-level data. Below we select from the ``User`` entity, producing a :class:`_sql.Select` that selects from the mapped :class:`_schema.Table` to which ``User`` is mapped:: @@ -213,7 +215,8 @@ as table columns are used:: ORDER BY user_account.id, address.id [...] (){stop} -ORM attributes, themselves known as :class:`_orm.InstrumentedAttribute` +ORM attributes, themselves known as +:class:`_orm.InstrumentedAttribute` objects, can be used in the same way as any :class:`_sql.ColumnElement`, and are delivered in result rows just the same way, such as below where we refer to their values by column name within each row:: @@ -428,18 +431,36 @@ JOIN elements in the resulting SQL:: JOIN order_items AS order_items_1 ON user_order.id = order_items_1.order_id JOIN item ON item.id = order_items_1.item_id -.. tip:: +The order in which each call to the :meth:`_sql.Select.join` method +is significant only to the degree that the "left" side of what we would like +to join from needs to be present in the list of FROMs before we indicate a +new target. :meth:`_sql.Select.join` would not, for example, know how to +join correctly if we were to specify +``select(User).join(Order.items).join(User.orders)``, and would raise an +error. In correct practice, the :meth:`_sql.Select.join` method is invoked +in such a way that lines up with how we would want the JOIN clauses in SQL +to be rendered, and each call should represent a clear link from what +precedes it. + +All of the elements that we target in the FROM clause remain available +as potential points to continue joining FROM. We can continue to add +other elements to join FROM the ``User`` entity above, for example adding +on the ``User.addresses`` relationship to our chain of joins:: + + >>> stmt = ( + ... select(User). + ... join(User.orders). + ... join(Order.items). + ... join(User.addresses) + ... ) + >>> print(stmt) + {opensql}SELECT user_account.id, user_account.name, user_account.fullname + FROM user_account + JOIN user_order ON user_account.id = user_order.user_id + JOIN order_items AS order_items_1 ON user_order.id = order_items_1.order_id + JOIN item ON item.id = order_items_1.item_id + JOIN address ON user_account.id = address.user_id - as seen in the above example, **the order in which each call to the join() - method occurs is important**. Query would not, for example, know how to - join correctly if we were to specify ``User``, then ``Item``, then - ``Order``, in our chain of joins; in such a case, depending on the - arguments passed, it may raise an error that it doesn't know how to join, - or it may produce invalid SQL in which case the database will raise an - error. In correct practice, the :meth:`_sql.Select.join` method is invoked - in such a way that lines up with how we would want the JOIN clauses in SQL - to be rendered, and each call should represent a clear link from what - precedes it. Joins to a Target Entity or Selectable ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -599,7 +620,6 @@ the ``Address`` entity and the custom subquery. Note we also apply a name ``"address"`` to the :func:`_orm.aliased` construct so that we may refer to it by name in the result row:: - >>> address_subq = aliased(Address, subq, name="address") >>> stmt = select(User, address_subq).join(address_subq) >>> for row in session.execute(stmt): @@ -639,7 +659,6 @@ to it using :class:`_orm.aliased` refer to distinct sets of columns:: User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='squirrel@squirrelpower.org') - Controlling what to Join From ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -675,8 +694,6 @@ be used:: WHERE user_account.name = :name_1 - - Special Relationship Operators ------------------------------ @@ -788,7 +805,7 @@ The ``populate_existing`` execution option is equvialent to the .. _orm_queryguide_autoflush: Autoflush -^^^^^^^^^^ +^^^^^^^^^ This option when passed as ``False`` will cause the :class:`_orm.Session` to not invoke the "autoflush" step. It's equivalent to using the @@ -813,7 +830,7 @@ The ``autoflush`` execution option is equvialent to the .. _orm_queryguide_yield_per: Yield Per -^^^^^^^^^^ +^^^^^^^^^ The ``yield_per`` execution option is an integer value which will cause the :class:`_engine.Result` to yield only a fixed count of rows at a time. It is @@ -842,29 +859,32 @@ rows (which are most). When ``yield_per`` is used, the :paramref:`_engine.Connection.execution_options.stream_results` option is also set for the Core execution, so that a streaming / server side cursor will be -used if the backend supports it (currently known are -:mod:`~sqlalchemy.dialects.postgresql.psycopg2`, -:mod:`~sqlalchemy.dialects.mysql.mysqldb` and -:mod:`~sqlalchemy.dialects.mysql.pymysql`. Other backends will pre buffer all -rows. The memory use of raw database rows is much less than that of an -ORM-mapped object, but should still be taken into consideration when -benchmarking. +used if the backend supports it [1]_ -The ``yield_per`` execution option **is not compatible subqueryload eager +The ``yield_per`` execution option **is not compatible with subqueryload eager loading or joinedload eager loading when using collections**. It is -potentially compatible with "select in" eager loading, **provided the database -driver supports multiple, independent cursors** (pysqlite and psycopg2 are -known to work, MySQL and SQL Server ODBC drivers do not). +potentially compatible with selectinload eager loading, **provided the database +driver supports multiple, independent cursors** [2]_ . The ``yield_per`` execution option is equvialent to the :meth:`_orm.Query.yield_per` method in :term:`1.x style` ORM queries. -.. seealso:: +.. [1] currently known are + :mod:`_postgresql.psycopg2`, + :mod:`_mysql.mysqldb` and + :mod:`_mysql.pymysql`. Other backends will pre buffer + all rows. The memory use of raw database rows is much less than that of an + ORM-mapped object, but should still be taken into consideration when + benchmarking. - :ref:`engine_stream_results` +.. [2] the :mod:`_postgresql.psycopg2` + and :mod:`_sqlite.pysqlite` drivers are + known to work, drivers for MySQL and SQL Server ODBC drivers do not. +.. seealso:: + :ref:`engine_stream_results` ORM Update / Delete with Arbitrary WHERE clause @@ -877,6 +897,3 @@ any number of database rows while also being able to synchronize the state of matching objects locally present in the :class:`_orm.Session`. See the section :ref:`orm_expression_update_delete` for background on this feature. - - - diff --git a/doc/build/orm/session_basics.rst b/doc/build/orm/session_basics.rst index 8cec8a18e0..d7fa9c81ab 100644 --- a/doc/build/orm/session_basics.rst +++ b/doc/build/orm/session_basics.rst @@ -556,7 +556,7 @@ Core :class:`_sql.Update` construct:: from sqlalchemy import update - stmt = update(User).where(User.nane == "squidward").values(name="spongebob")).\ + stmt = update(User).where(User.name == "squidward").values(name="spongebob")).\ execution_options(synchronize_session="fetch") session.execute(stmt) @@ -570,14 +570,14 @@ within the :class:`_orm.Session` will be marked as deleted and expunged. ORM-enabled delete, :term:`1.x style`:: - session.query(User).filter(User.nane == "squidward").\ + session.query(User).filter(User.name == "squidward").\ delete(synchronize_session="fetch") ORM-enabled delete, :term:`2.0 style`:: from sqlalchemy import delete - stmt = delete(User).where(User.nane == "squidward").execution_options(synchronize_session="fetch") + stmt = delete(User).where(User.name == "squidward").execution_options(synchronize_session="fetch") session.execute(stmt) diff --git a/doc/build/orm/session_transaction.rst b/doc/build/orm/session_transaction.rst index 3f3781f8dd..7daa47ef74 100644 --- a/doc/build/orm/session_transaction.rst +++ b/doc/build/orm/session_transaction.rst @@ -235,8 +235,8 @@ Session:: ]) session.commit() -Commit at once -~~~~~~~~~~~~~~~~ +Begin Once +~~~~~~~~~~ Both :class:`_orm.sessionmaker` and :class:`_future.Engine` feature a :meth:`_future.Engine.begin` method that will both procure a new object diff --git a/doc/build/tutorial/data.rst b/doc/build/tutorial/data.rst index 55d65c4f47..e7136683b5 100644 --- a/doc/build/tutorial/data.rst +++ b/doc/build/tutorial/data.rst @@ -24,13 +24,14 @@ The components of this section are as follows: * :ref:`tutorial_core_insert` - to get some data into the database, we introduce and demonstrate the Core :class:`_sql.Insert` construct. INSERTs from an - ORM perspective are described later, at :ref:`tutorial_orm_data_manipulation`. + ORM perspective are described in the next section + :ref:`tutorial_orm_data_manipulation`. * :ref:`tutorial_selecting_data` - this section will describe in detail the :class:`_sql.Select` construct, which is the most commonly used object in SQLAlchemy. The :class:`_sql.Select` construct emits SELECT statements for both Core and ORM centric applications and both use cases will be - described here. Additional ORM use cases are also noted in he later + described here. Additional ORM use cases are also noted in the later section :ref:`tutorial_select_relationships` as well as the :ref:`queryguide_toplevel`. @@ -64,7 +65,7 @@ new data into a table. The insert() SQL Expression Construct ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -A simple example of :class:`_sql.Insert` illustrates the target table +A simple example of :class:`_sql.Insert` illustrating the target table and the VALUES clause at once:: >>> from sqlalchemy import insert @@ -248,9 +249,7 @@ as well as the values for server defaults. However the RETURNING clause may also be specified explicitly using the :meth:`_sql.Insert.returning` method; in this case, the :class:`_engine.Result` object that's returned when the statement is executed has rows which -can be fetched. It is only supported for single-statement -forms, and for some backends may only support single-row INSERT statements -overall:: +can be fetched:: >>> insert_stmt = insert(address_table).returning(address_table.c.id, address_table.c.email_address) >>> print(insert_stmt) @@ -258,7 +257,6 @@ overall:: VALUES (:id, :user_id, :email_address) RETURNING address.id, address.email_address - It can also be combined with :meth:`_sql.Insert.from_select`, as in the example below that builds upon the example stated in :ref:`tutorial_insert_from_select`:: @@ -272,6 +270,27 @@ as in the example below that builds upon the example stated in SELECT user_account.id, user_account.name || :name_1 AS anon_1 FROM user_account RETURNING address.id, address.email_address +.. tip:: + + The RETURNING feature is also supported by UPDATE and DELETE statements, + which will be introduced later in this tutorial. + The RETURNING feature is generally [1]_ only + supported for statement executions that use a single set of bound + parameters; that is, it wont work with the "executemany" form introduced + at :ref:`tutorial_multiple_parameters`. Additionally, some dialects + such as the Oracle dialect only allow RETURNING to return a single row + overall, meaning it won't work with "INSERT..FROM SELECT" nor will it + work with multiple row :class:`_sql.Update` or :class:`_sql.Delete` + forms. + + .. [1] There is internal support for the + :mod:`_postgresql.psycopg2` dialect to INSERT many rows at once + and also support RETURNING, which is leveraged by the SQLAlchemy + ORM. However this feature has not been generalized to all dialects + and is not yet part of SQLAlchemy's regular API. + + + .. seealso:: :class:`_sql.Insert` - in the SQL Expression API documentation @@ -354,6 +373,16 @@ complete entities, such as instances of the ``User`` class, as column values: (User(id=1, name='spongebob', fullname='Spongebob Squarepants'),) {opensql}ROLLBACK{stop} +.. topic:: select() from a Table vs. ORM class + + While the SQL generated in these examples looks the same whether we invoke + ``select(user_table)`` or ``select(User)``, in the more general case + they do not necessarily render the same thing, as an ORM-mapped class + may be mapped to other kinds of "selectables" besides tables. The + ``select()`` that's against an ORM entity also indicates that ORM-mapped + instances should be returned in a result, which is not the case when + SELECTing from a :class:`_schema.Table` object. + The following sections will discuss the SELECT construct in more detail. @@ -455,7 +484,7 @@ or ``user_id > 10``, by making use of standard Python operators in conjunction with :class:`_schema.Column` and similar objects. For boolean expressions, most Python operators such as ``==``, ``!=``, ``<``, ``>=`` etc. generate new -SQL Expression objects, rather than plain boolean True/False values:: +SQL Expression objects, rather than plain boolean ``True``/``False`` values:: >>> print(user_table.c.name == 'squidward') user_account.name = :name_1 @@ -561,9 +590,10 @@ clause:: {opensql}SELECT user_account.name, address.email_address FROM user_account, address -In order to JOIN these two tables together, two methods that are -most straightforward are :meth:`_sql.Select.join_from`, which -allows us to indicate the left and right side of the JOIN explicitly:: +In order to JOIN these two tables together, we typically use one of two methods +on :class:`_sql.Select`. The first is the :meth:`_sql.Select.join_from` +method, which allows us to indicate the left and right side of the JOIN +explicitly:: >>> print( ... select(user_table.c.name, address_table.c.email_address). @@ -573,7 +603,7 @@ allows us to indicate the left and right side of the JOIN explicitly:: FROM user_account JOIN address ON user_account.id = address.user_id -the other is the :meth:`_sql.Select.join` method, which indicates only the +The other is the the :meth:`_sql.Select.join` method, which indicates only the right side of the JOIN, the left hand-side is inferred:: >>> print( @@ -586,8 +616,8 @@ right side of the JOIN, the left hand-side is inferred:: .. sidebar:: The ON Clause is inferred When using :meth:`_sql.Select.join_from` or :meth:`_sql.Select.join`, we may - observe that the ON clause of the join is also inferred for us in simple cases. - More on that in the next section. + observe that the ON clause of the join is also inferred for us in simple + foreign key cases. More on that in the next section. We also have the option add elements to the FROM clause explicitly, if it is not inferred the way we want from the columns clause. We use the @@ -621,7 +651,7 @@ produce the SQL ``count()`` function:: Setting the ON Clause ~~~~~~~~~~~~~~~~~~~~~ -The previous examples on JOIN illustrated that the :class:`_sql.Select` construct +The previous examples of JOIN illustrated that the :class:`_sql.Select` construct can join between two tables and produce the ON clause automatically. This occurs in those examples because the ``user_table`` and ``address_table`` :class:`_sql.Table` objects include a single :class:`_schema.ForeignKeyConstraint` @@ -644,9 +674,10 @@ same SQL Expression mechanics as we saw about in :ref:`tutorial_select_where_cla .. container:: orm-header **ORM Tip** - there's another way to generate the ON clause when using - ORM entities as well, when using the :func:`_orm.relationship` construct - that can be seen in the mapping set up at :ref:`tutorial_declaring_mapped_classes`. - This is a whole subject onto itself, which is introduced more fully + ORM entities that make use of the :func:`_orm.relationship` construct, + like the mapping set up in the previous section at + :ref:`tutorial_declaring_mapped_classes`. + This is a whole subject onto itself, which is introduced at length at :ref:`tutorial_joining_relationships`. OUTER and FULL join @@ -661,17 +692,22 @@ and FULL OUTER JOIN, respectively:: ... select(user_table).join(address_table, isouter=True) ... ) {opensql}SELECT user_account.id, user_account.name, user_account.fullname - FROM user_account LEFT OUTER JOIN address ON user_account.id = address.user_id + FROM user_account LEFT OUTER JOIN address ON user_account.id = address.user_id{stop} >>> print( ... select(user_table).join(address_table, full=True) ... ) {opensql}SELECT user_account.id, user_account.name, user_account.fullname - FROM user_account FULL OUTER JOIN address ON user_account.id = address.user_id + FROM user_account FULL OUTER JOIN address ON user_account.id = address.user_id{stop} There is also a method :meth:`_sql.Select.outerjoin` that is equivalent to using ``.join(..., isouter=True)``. +.. tip:: + + SQL also has a "RIGHT OUTER JOIN". SQLAlchemy doesn't render this directly; + instead, reverse the order of the tables and use "LEFT OUTER JOIN". + ORDER BY ^^^^^^^^^ @@ -706,8 +742,8 @@ value in a set of values. SQLAlchemy provides for SQL functions in an open-ended way using a namespace known as :data:`_sql.func`. This is a special constructor object which will create new instances of :class:`_functions.Function` when given the name -of a particular SQL function, which can be any name, as well as zero or -more arguments to pass to the function, which are like in all other cases +of a particular SQL function, which can have any name, as well as zero or +more arguments to pass to the function, which are, like in all other cases, SQL Expression constructs. For example, to render the SQL COUNT() function against the ``user_account.id`` column, we call upon the name ``count()`` name:: @@ -752,7 +788,7 @@ than one address: Ordering or Grouping by a Label ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -An important technique in particular on some database backends is the ability +An important technique, in particular on some database backends, is the ability to ORDER BY or GROUP BY an expression that is already stated in the columns clause, without re-stating the expression in the ORDER BY or GROUP BY clause and instead using the column name or labeled name from the COLUMNS clause. @@ -946,6 +982,13 @@ in a "recursive" style, and may in more elaborate cases be composed from the RETURNING clause of an INSERT, UPDATE or DELETE statement. The docstring for :class:`_sql.CTE` includes details on these additional patterns. +In both cases, the subquery and CTE were named at the SQL level using an +"anonymous" name. In the Python code, we don't need to provide these names +at all. The object identity of the :class:`_sql.Subquery` or :class:`_sql.CTE` +instances serves as the syntactical identity of the object when rendered. +A name that will be rendered in the SQL can be provided by passing it as the +first argument of the :meth:`_sql.Select.subquery` or :meth:`_sql.Select.cte` methods. + .. seealso:: :meth:`_sql.Select.subquery` - further detail on subqueries @@ -1021,11 +1064,6 @@ Another example follows, which is exactly the same except it makes use of the User(id=2, name='sandy', fullname='Sandy Cheeks') Address(id=3, email_address='sandy@squirrelpower.org') {opensql}ROLLBACK{stop} -In both cases, the subquery and CTE were named at the SQL level using an -"anonymous" name. In the Python code, we don't need to provide these names -at all. The object identity of the :class:`_sql.Subquery` or :class:`_sql.CTE` -instances serves as the syntactical identity of the object when rendered. - .. _tutorial_scalar_subquery: Scalar and Correlated Subqueries @@ -1385,7 +1423,8 @@ tuples so that this order may be controlled [1]_:: {opensql}UPDATE some_table SET y=:y, x=(some_table.y + :y_1) -.. [1] While Python dictionaries are `guaranteed to be insert ordered +.. [1] While Python dictionaries are + `guaranteed to be insert ordered `_ as of Python 3.7, the :meth:`_sql.Update.ordered_values` method stilll provides an additional @@ -1403,14 +1442,12 @@ delete rows from a table. The :func:`_sql.delete` statement from an API perspective is very similar to that of the :func:`_sql.update` construct, traditionally returning no rows but -allowing for a RETURNING variant. +allowing for a RETURNING variant on some database backends. :: >>> from sqlalchemy import delete - >>> stmt = ( - ... delete(user_table).where(user_table.c.name == 'patrick') - ... ) + >>> stmt = delete(user_table).where(user_table.c.name == 'patrick') >>> print(stmt) {opensql}DELETE FROM user_account WHERE user_account.name = :name_1 @@ -1511,7 +1548,7 @@ be iterated:: >>> print(update_stmt) {opensql}UPDATE user_account SET fullname=:fullname WHERE user_account.name = :name_1 - RETURNING user_account.id, user_account.name + RETURNING user_account.id, user_account.name{stop} >>> delete_stmt = ( ... delete(user_table).where(user_table.c.name == 'patrick'). @@ -1520,7 +1557,7 @@ be iterated:: >>> print(delete_stmt) {opensql}DELETE FROM user_account WHERE user_account.name = :name_1 - RETURNING user_account.id, user_account.name + RETURNING user_account.id, user_account.name{stop} Further Reading for UPDATE, DELETE ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/build/tutorial/dbapi_transactions.rst b/doc/build/tutorial/dbapi_transactions.rst index 24df539431..d9b20c7605 100644 --- a/doc/build/tutorial/dbapi_transactions.rst +++ b/doc/build/tutorial/dbapi_transactions.rst @@ -337,6 +337,19 @@ which is one of six different formats allowed by the DBAPI specification. SQLAlchemy abstracts these formats into just one, which is the "named" format using a colon. +.. topic:: Always use bound parameters + + As mentioned at the beginning of this section, textual SQL is not the usual + way we work with SQLAlchemy. However, when using textual SQL, a Python + literal value, even non-strings like integers or dates, should **never be + stringified into SQL string directly**; a parameter should **always** be + used. This is most famously known as how to avoid SQL injection attacks + when the data is untrusted. However it also allows the SQLAlchemy dialects + and/or DBAPI to correctly handle the incoming input for the backend. + Outside of plain textual SQL use cases, SQLAlchemy's Core Expression API + otherwise ensures that Python literal values are passed as bound parameters + where appropriate. + .. _tutorial_multiple_parameters: Sending Multiple Parameters diff --git a/doc/build/tutorial/engine.rst b/doc/build/tutorial/engine.rst index 55cd9acfd2..a23cc939f8 100644 --- a/doc/build/tutorial/engine.rst +++ b/doc/build/tutorial/engine.rst @@ -41,7 +41,8 @@ facts: driver that SQLAlchemy uses to interact with a particular database. In this case, we're using the name ``pysqlite``, which in modern Python use is the `sqlite3 `_ standard - library interface for SQLite. + library interface for SQLite. If omitted, SQLAlchemy will use a default + :term:`DBAPI` for the particular database selected. 3. How do we locate the database? In this case, our URL includes the phrase ``/:memory:``, which is an indicator to the ``sqlite3`` module that we diff --git a/doc/build/tutorial/index.rst b/doc/build/tutorial/index.rst index 8547e7f1d6..d7f5138606 100644 --- a/doc/build/tutorial/index.rst +++ b/doc/build/tutorial/index.rst @@ -29,7 +29,7 @@ SQLAlchemy 1.4 / 2.0 Tutorial within the 1.4 transitional phase should check out the :ref:`migration_20_toplevel` document as well. - For the newcomer, this document has a **lot** of detail, however at the + For the newcomer, this document has a **lot** of detail, however by the end they will be considered an **Alchemist**. SQLAlchemy is presented as two distinct APIs, one building on top of the other. @@ -104,7 +104,7 @@ The major sections of this tutorial are as follows: * :ref:`tutorial_working_with_data` - here we learn how to create, select, update and delete data in the database. The so-called :term:`CRUD` operations here are given in terms of SQLAlchemy Core with links out towards - their ORM counterparts. The SELECT operation is deeply introduced at + their ORM counterparts. The SELECT operation that is introduced in detail at :ref:`tutorial_selecting_data` applies equally well to Core and ORM. * :ref:`tutorial_orm_data_manipulation` covers the persistence framework of the @@ -116,7 +116,7 @@ The major sections of this tutorial are as follows: of how it's used, with links to deeper documentation. * :ref:`tutorial_further_reading` lists a series of major top-level - documentation sections which fully document the concepts introduced in this + documentation sections which fully documents the concepts introduced in this tutorial. diff --git a/doc/build/tutorial/orm_data_manipulation.rst b/doc/build/tutorial/orm_data_manipulation.rst index 469d1096bf..6068ec4fd4 100644 --- a/doc/build/tutorial/orm_data_manipulation.rst +++ b/doc/build/tutorial/orm_data_manipulation.rst @@ -61,8 +61,9 @@ names as keys in the constructor. In a similar manner as in our Core examples of :class:`_sql.Insert`, we did not include a primary key (i.e. an entry for the ``id`` column), since we would -like to make use of SQLite's auto-incrementing primary key feature which the -ORM also integrates with. The value of the ``id`` attribute on the above +like to make use of the auto-incrementing primary key feature of the database, +SQLite in this case, which the ORM also integrates with. +The value of the ``id`` attribute on the above objects, if we were to view it, displays itself as ``None``:: >>> squidward @@ -126,10 +127,11 @@ method: INSERT INTO user_account (name, fullname) VALUES (?, ?) [...] ('ehkrabs', 'Eugene H. Krabs') -Above we observe the :class:`_orm.Session` was first called upon to emit -SQL, so it created a new transaction and emitted the appropriate INSERT -statements for the two objects. The transaction now **remains open** -until we call the :meth:`_orm.Session.commit` method. +Above we observe the :class:`_orm.Session` was first called upon to emit SQL, +so it created a new transaction and emitted the appropriate INSERT statements +for the two objects. The transaction now **remains open** until we call any +of the :meth:`_orm.Session.commit`, :meth:`_orm.Session.rollback`, or +:meth:`_orm.Session.close` methods of :class:`_orm.Session`. While :meth:`_orm.Session.flush` may be used to manually push out pending changes to the current transaction, it is usually unnecessary as the @@ -223,7 +225,7 @@ using the ORM, there are two ways in which this construct is used. The primary way is that it is emitted automatically as part of the :term:`unit of work` process used by the :class:`_orm.Session`, where an UPDATE statement is emitted on a per-primary key basis corresponding to individual objects that have -changes on them. A second form of ORM enabled UPDATE is called an "ORM enabled +changes on them. A second form of UPDATE is called an "ORM enabled UPDATE" and allows us to use the :class:`_sql.Update` construct with the :class:`_orm.Session` explicitly; this is described in the next section. @@ -413,8 +415,8 @@ ORM-enabled DELETE Statements Like UPDATE operations, there is also an ORM-enabled version of DELETE which we can illustrate by using the :func:`_sql.delete` construct with :meth:`_orm.Session.execute`. It also has a feature by which **non expired** -objects that match the given deletion criteria will be automatically marked -as "deleted" in the :class:`_orm.Session`: +objects (see :term:`expired`) that match the given deletion criteria will be +automatically marked as ":term:`deleted`" in the :class:`_orm.Session`: .. sourcecode:: pycon+sql @@ -469,7 +471,7 @@ with the exception of a special SQLAlchemy internal state object:: >>> sandy.__dict__ {'_sa_instance_state': } -This is the "expired" state; accessing the attribute again will autobegin +This is the ":term:`expired`" state; accessing the attribute again will autobegin a new transaction and refresh ``sandy`` with the current database row: .. sourcecode:: pycon+sql diff --git a/doc/build/tutorial/orm_related_objects.rst b/doc/build/tutorial/orm_related_objects.rst index 120492c28d..2afabc6542 100644 --- a/doc/build/tutorial/orm_related_objects.rst +++ b/doc/build/tutorial/orm_related_objects.rst @@ -8,9 +8,9 @@ .. _tutorial_orm_related_objects: Working with Related Objects -============================= +============================ -In this section, we will cover one more essential ORM concept, which is that of +In this section, we will cover one more essential ORM concept, which is how the ORM interacts with mapped classes that refer to other objects. In the section :ref:`tutorial_declaring_mapped_classes`, the mapped class examples made use of a construct called :func:`_orm.relationship`. This construct @@ -170,7 +170,7 @@ objects are not yet associated with a real database row:: It's at this stage that we can see the very great utility that the unit of work process provides; recall in the section :ref:`tutorial_core_insert_values_clause`, -rows were inserted rows into the ``user_account`` and +rows were inserted into the ``user_account`` and ``address`` tables using some elaborate syntaxes in order to automatically associate the ``address.user_id`` columns with those of the ``user_account`` rows. Additionally, it was necessary that we emit INSERT for ``user_account`` @@ -199,7 +199,7 @@ newly generated primary key of the ``user_account`` row is applied to the .. _tutorial_loading_relationships: Loading Relationships ----------------------- +--------------------- In the last step, we called :meth:`_orm.Session.commit` which emitted a COMMIT for the transaction, and then per @@ -266,11 +266,11 @@ section at :ref:`tutorial_orm_loader_strategies`. .. _tutorial_select_relationships: Using Relationships in Queries -------------------------------- +------------------------------ The previous section introduced the behavior of the :func:`_orm.relationship` construct when working with **instances of a mapped class**, above, the -``u1``, ``a1`` and ``a2`` instances of the ``User`` and ``Address`` class. +``u1``, ``a1`` and ``a2`` instances of the ``User`` and ``Address`` classes. In this section, we introduce the behavior of :func:`_orm.relationship` as it applies to **class level behavior of a mapped class**, where it serves in several ways to help automate the construction of SQL queries. @@ -323,7 +323,7 @@ between the two mapped :class:`_schema.Table` objects, not because of the .. _tutorial_joining_relationships_aliased: Joining between Aliased targets -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the section :ref:`tutorial_orm_entity_aliases` we introduced the :func:`_orm.aliased` construct, which is used to apply a SQL alias to an @@ -391,8 +391,8 @@ email addresses: .. _tutorial_relationship_exists: -EXISTS forms / has() / any() -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +EXISTS forms: has() / any() +^^^^^^^^^^^^^^^^^^^^^^^^^^^ In the section :ref:`tutorial_exists`, we introduced the :class:`_sql.Exists` object that provides for the SQL EXISTS keyword in conjunction with a @@ -463,7 +463,7 @@ which belonged to "pearl": .. _tutorial_relationship_operators: Common Relationship Operators -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ There are some additional varieties of SQL generation helpers that come with :func:`_orm.relationship`, including: @@ -587,7 +587,7 @@ loader strategies. loader strategies Selectin Load -^^^^^^^^^^^^^^ +^^^^^^^^^^^^^ The most useful loader in modern SQLAlchemy is the :func:`_orm.selectinload` loader option. This option solves the most common @@ -667,7 +667,8 @@ as below where we know that all ``Address`` objects have an associated pearl.krabs@gmail.com pkrabs pearl@aol.com pkrabs -:func:`_orm.joinedload` also works for collections, however it has the effect +:func:`_orm.joinedload` also works for collections, meaning one-to-many relationships, +however it has the effect of multiplying out primary rows per related item in a recursive way that grows the amount of data sent for a result set by orders of magnitude for nested collections and/or larger collections, so its use vs. another option @@ -777,7 +778,7 @@ arbitrary criteria to a JOIN rendered with :func:`_orm.relationship` to also include additional criteria in the ON clause. The :meth:`_orm.PropComparator.and_` method is in fact generally available for most loader options. For example, if we wanted to re-load the names of users and their email addresses, but omitting -the email addresses at the ``sqlalchemy.org`` domain, we can apply +the email addresses with the ``sqlalchemy.org`` domain, we can apply :meth:`_orm.PropComparator.and_` to the argument passed to :func:`_orm.selectinload` to limit this criteria: @@ -839,7 +840,7 @@ Raiseload One additional loader strategy worth mentioning is :func:`_orm.raiseload`. This option is used to completely block an application from having the :term:`N plus one` problem at all by causing what would normally be a lazy -load to raise instead. It has two variants that are controlled via +load to raise an error instead. It has two variants that are controlled via the :paramref:`_orm.raiseload.sql_only` option to block either lazy loads that require SQL, versus all "load" operations including those which only need to consult the current :class:`_orm.Session`. diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py index e36797d47f..7d35856f3f 100644 --- a/lib/sqlalchemy/orm/__init__.py +++ b/lib/sqlalchemy/orm/__init__.py @@ -16,6 +16,10 @@ documentation for an overview of how this module is used. from . import exc # noqa from . import mapper as mapperlib # noqa from . import strategy_options +from .attributes import AttributeEvent # noqa +from .attributes import InstrumentedAttribute # noqa +from .attributes import QueryableAttribute # noqa +from .context import QueryContext # noqa from .decl_api import as_declarative # noqa from .decl_api import declarative_base # noqa from .decl_api import declared_attr # noqa @@ -24,14 +28,21 @@ from .decl_api import registry # noqa from .decl_api import synonym_for # noqa from .descriptor_props import CompositeProperty # noqa from .descriptor_props import SynonymProperty # noqa +from .identity import IdentityMap # noqa +from .instrumentation import ClassManager # noqa from .interfaces import EXT_CONTINUE # noqa from .interfaces import EXT_SKIP # noqa from .interfaces import EXT_STOP # noqa +from .interfaces import InspectionAttr # noqa +from .interfaces import InspectionAttrInfo # noqa from .interfaces import MANYTOMANY # noqa from .interfaces import MANYTOONE # noqa from .interfaces import MapperProperty # noqa +from .interfaces import NOT_EXTENSION # noqa from .interfaces import ONETOMANY # noqa from .interfaces import PropComparator # noqa +from .loading import merge_frozen_result # noqa +from .loading import merge_result # noqa from .mapper import _mapper_registry from .mapper import class_mapper # noqa from .mapper import configure_mappers # noqa @@ -54,9 +65,13 @@ from .session import ORMExecuteState # noqa from .session import Session # noqa from .session import sessionmaker # noqa from .session import SessionTransaction # noqa +from .state import AttributeState # noqa +from .state import InstanceState # noqa from .strategy_options import Load # noqa +from .unitofwork import UOWTransaction # noqa from .util import aliased # noqa from .util import Bundle # noqa +from .util import CascadeOptions # noqa from .util import join # noqa from .util import LoaderCriteriaOption # noqa from .util import object_mapper # noqa diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index c7ef97c6cd..4280cb76e6 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -512,7 +512,7 @@ OP_BULK_REPLACE = util.symbol("BULK_REPLACE") OP_MODIFIED = util.symbol("MODIFIED") -class Event(object): +class AttributeEvent(object): """A token propagated throughout the course of a chain of attribute events. @@ -549,7 +549,7 @@ class Event(object): def __eq__(self, other): return ( - isinstance(other, Event) + isinstance(other, AttributeEvent) and other.impl is self.impl and other.op == self.op ) @@ -562,6 +562,9 @@ class Event(object): return self.impl.hasparent(state) +Event = AttributeEvent + + class AttributeImpl(object): """internal implementation for instrumented attributes.""" diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py index 51a81fe021..b755eef73c 100644 --- a/lib/sqlalchemy/sql/dml.py +++ b/lib/sqlalchemy/sql/dml.py @@ -1040,7 +1040,7 @@ class DMLWhereBase(object): """ - for criterion in list(whereclause): + for criterion in whereclause: where_criteria = coercions.expect(roles.WhereHavingRole, criterion) self._where_criteria += (where_criteria,) diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index b5dbed1a94..086cef48a9 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -4988,7 +4988,7 @@ class Select( assert isinstance(self._where_criteria, tuple) - for criterion in list(whereclause): + for criterion in whereclause: where_criteria = coercions.expect(roles.WhereHavingRole, criterion) self._where_criteria += (where_criteria,)