]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Some small improvements on the tutorial 2.0 documents
authorFederico Caselli <cfederico87@gmail.com>
Tue, 3 Nov 2020 22:24:28 +0000 (23:24 +0100)
committerFederico Caselli <cfederico87@gmail.com>
Fri, 13 Nov 2020 22:03:01 +0000 (23:03 +0100)
Change-Id: I7fb37d45c29307b2213bebd0ef280d73804ac473

17 files changed:
doc/build/core/operators.rst
doc/build/glossary.rst
doc/build/orm/extensions/asyncio.rst
doc/build/orm/internals.rst
doc/build/orm/queryguide.rst
doc/build/orm/session_basics.rst
doc/build/orm/session_transaction.rst
doc/build/tutorial/data.rst
doc/build/tutorial/dbapi_transactions.rst
doc/build/tutorial/engine.rst
doc/build/tutorial/index.rst
doc/build/tutorial/orm_data_manipulation.rst
doc/build/tutorial/orm_related_objects.rst
lib/sqlalchemy/orm/__init__.py
lib/sqlalchemy/orm/attributes.py
lib/sqlalchemy/sql/dml.py
lib/sqlalchemy/sql/selectable.py

index 5972b53d5faff08a46c09756b79bdba9ef7c9974..ac9633b77631a3f8f2b1884dfe3b1cb5338444d5 100644 (file)
@@ -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:
 
index d47f0397a0645878fe72853ac3ae2cd05144a1b5..f5ebe0a7daa3187e1bdd99140eba8b9431ee743c 100644 (file)
@@ -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.
 
index 2afda1c10268b9abedfd84d7a30bb80c26df0d11..a22529ec5e716e1d5b524a1a03d65db312ec80b9 100644 (file)
@@ -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.
index 3fdfe500ee0254b394ea74b17313f5644c645715..8f26f7c3c0303bb11765719fa1e1ce1c3e7a65b1 100644 (file)
@@ -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:
 
index 5576882a6c40a9f4ea929ba8f495e23fd1984464..1ae44e23c63eb3b9433324a9c5a7b38f5af2356e 100644 (file)
@@ -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.
 
-
-
-
index 8cec8a18e0b205cff3b99cf1e4ea56f79d530b28..d7fa9c81ab487b98e5c1bf54212af868cad51535 100644 (file)
@@ -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)
 
index 3f3781f8ddd78ed60f303b88098ac0071a404fde..7daa47ef74d93195ed27c960985b0f8213bc1283 100644 (file)
@@ -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
index 55d65c4f47a1ddbb8b31af2080db801ea3fb1075..e7136683b5f5ec96a314fd54e67fd53424f95f5a 100644 (file)
@@ -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
    <https://mail.python.org/pipermail/python-dev/2017-December/151283.html>`_
    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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
index 24df5394312d0fbec0370cf19bf6ba412c36a95d..d9b20c760553ee97bf0903cac4dae1c1c84655e3 100644 (file)
@@ -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
index 55cd9acfd22846b3d40f2d441ac0044c0d04959c..a23cc939f86c4433836408500eb50e958d62045c 100644 (file)
@@ -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 <http://docs.python.org/library/sqlite3.html>`_ 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
index 8547e7f1d6dd0092c427ee953b72be1333b190ca..d7f513860677ddc841ab8770e9f0e2eac5937cfc 100644 (file)
@@ -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.
 
 
index 469d1096bf02ea7c410a5290c7ff218954d28de0..6068ec4fd4912a5990234f8274f75dc194fe4ceb 100644 (file)
@@ -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': <sqlalchemy.orm.state.InstanceState object at 0x...>}
 
-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
index 120492c28db06b8d0e7ed5502d3ca7ba42930a36..2afabc6542369497a88c73ff0225a3ed6c334796 100644 (file)
@@ -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`.
index e36797d47fc135aab994ca6f2aae3e9d6436611a..7d35856f3f3b02c4e05c099465a6557c369b4a52 100644 (file)
@@ -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
index c7ef97c6cd12ac9a35619a33cb47a5fab639770d..4280cb76e6ecd09a2bd97353bd7998346fd87265 100644 (file)
@@ -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."""
 
index 51a81fe021b14221f47ae0e5d28ef2c2bccd88b1..b755eef73cad37f0fa2dd525feaacb81732aec49 100644 (file)
@@ -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,)
 
index b5dbed1a94cba53dd7490d37c6a47647bd6063a5..086cef48a905525e9e64b163ae9ab4dff838378c 100644 (file)
@@ -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,)