From: Mike Bayer Date: Sat, 4 Jun 2022 19:53:34 +0000 (-0400) Subject: migrate labels to new tutorial X-Git-Tag: rel_2_0_0b1~262^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1c99edf1b988f55411bd8bef917b9664a39d218b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git migrate labels to new tutorial other org changes and some sections from old tutorial ported to new tutorial. Change-Id: Ic0fba60ec82fff481890887beef9ed0fa271875a --- diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index 95a5da35b9..1db674078f 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -811,7 +811,7 @@ .. seealso:: - :ref:`updates_order_parameters` + :ref:`tutorial_parameter_ordered_updates` .. change:: :tags: bug, orm diff --git a/doc/build/changelog/migration_11.rst b/doc/build/changelog/migration_11.rst index a2c88ae11d..5c1b842b61 100644 --- a/doc/build/changelog/migration_11.rst +++ b/doc/build/changelog/migration_11.rst @@ -1213,7 +1213,7 @@ RANGE and ROWS expressions for window functions:: .. seealso:: - :ref:`window_functions` + :ref:`tutorial_window_functions` :ticket:`3049` @@ -1242,7 +1242,7 @@ selectable, e.g. lateral correlation:: .. seealso:: - :ref:`lateral_selects` + :ref:`tutorial_lateral_correlation` :class:`_expression.Lateral` @@ -1478,7 +1478,7 @@ this behavioral change for applications using it are at :ref:`behavior_change_35 .. seealso:: - :ref:`sqlexpression_text_columns` - in the Core tutorial + :ref:`tutorial_select_arbitrary_text` :ref:`behavior_change_3501` - backwards compatibility remarks @@ -2606,9 +2606,6 @@ Support for PyGreSQL The `PyGreSQL `_ DBAPI is now supported. -.. seealso:: - - :ref:`dialect-postgresql-pygresql` The "postgres" module is removed -------------------------------- diff --git a/doc/build/changelog/migration_12.rst b/doc/build/changelog/migration_12.rst index bc1d0739e9..7073660f78 100644 --- a/doc/build/changelog/migration_12.rst +++ b/doc/build/changelog/migration_12.rst @@ -905,7 +905,7 @@ would render as:: .. seealso:: - :ref:`multi_table_deletes` + :ref:`tutorial_multi_table_deletes` :ticket:`959` diff --git a/doc/build/changelog/migration_20.rst b/doc/build/changelog/migration_20.rst index 6e9aae04a5..05ef1dcc71 100644 --- a/doc/build/changelog/migration_20.rst +++ b/doc/build/changelog/migration_20.rst @@ -583,8 +583,8 @@ Given the example program below:: The above program uses several patterns that many users will already identify as "legacy", namely the use of the :meth:`_engine.Engine.execute` method -that's part of the :ref:`connectionless execution ` -system. When we run the above program against 1.4, it returns a single line:: +that's part of the "connectionless execution" API. When we run the above +program against 1.4, it returns a single line:: $ python test3.py [(1,)] @@ -2498,11 +2498,84 @@ explicit use of :meth:`_orm.Session.begin`, which is now solved by 1.4, as well as to allow the use of "subtransactions", which are also removed in 2.0. +.. _migration_20_session_subtransaction: + Session "subtransaction" behavior removed ------------------------------------------ -See the section :ref:`session_subtransactions` for background on this -change. +**Synopsis** + +The "subtransaction" pattern that was often used with autocommit mode is +also deprecated in 1.4. This pattern allowed the use of the +:meth:`_orm.Session.begin` method when a transaction were already begun, +resulting in a construct called a "subtransaction", which was essentially +a block that would prevent the :meth:`_orm.Session.commit` method from actually +committing. + +**Migration to 2.0** + + +To provide backwards compatibility for applications that make use of this +pattern, the following context manager or a similar implementation based on +a decorator may be used:: + + + import contextlib + + @contextlib.contextmanager + def transaction(session): + if not session.in_transaction(): + with session.begin(): + yield + else: + yield + + +The above context manager may be used in the same way the +"subtransaction" flag works, such as in the following example:: + + + # method_a starts a transaction and calls method_b + def method_a(session): + with transaction(session): + method_b(session) + + # method_b also starts a transaction, but when + # called from method_a participates in the ongoing + # transaction. + def method_b(session): + with transaction(session): + session.add(SomeObject('bat', 'lala')) + + Session = sessionmaker(engine) + + # create a Session and call method_a + with Session() as session: + method_a(session) + +To compare towards the preferred idiomatic pattern, the begin block should +be at the outermost level. This removes the need for individual functions +or methods to be concerned with the details of transaction demarcation:: + + def method_a(session): + method_b(session) + + def method_b(session): + session.add(SomeObject('bat', 'lala')) + + Session = sessionmaker(engine) + + # create a Session and call method_a + with Session() as session: + with session.begin(): + method_a(session) + +**Discussion** + +This pattern has been shown to be confusing in real world applications, and it +is preferable for an application to ensure that the top-most level of database +operations are performed with a single begin/commit pair. + 2.0 Migration - ORM Extension and Recipe Changes diff --git a/doc/build/changelog/unreleased_20/7998.rst b/doc/build/changelog/unreleased_20/7998.rst index 9c88903f20..dff34093df 100644 --- a/doc/build/changelog/unreleased_20/7998.rst +++ b/doc/build/changelog/unreleased_20/7998.rst @@ -2,18 +2,18 @@ :tags: usecase, sql :tickets: 7998 - Altered the compilation mechanics of the :class:`.Insert` construct such - that the "autoincrement primary key" column value will be fetched via + Altered the compilation mechanics of the :class:`_dml.Insert` construct + such that the "autoincrement primary key" column value will be fetched via ``cursor.lastrowid`` or RETURNING even if present in the parameter set or - within the :meth:`.Insert.values` method as a plain bound value, for + within the :meth:`_dml.Insert.values` method as a plain bound value, for single-row INSERT statements on specific backends that are known to generate autoincrementing values even when explicit NULL is passed. This restores a behavior that was in the 1.3 series for both the use case of - separate parameter set as well as :meth:`.Insert.values`. In 1.4, the + separate parameter set as well as :meth:`_dml.Insert.values`. In 1.4, the parameter set behavior unintentionally changed to no longer do this, but - the :meth:`.Insert.values` method would still fetch autoincrement values up - until 1.4.21 where :ticket:`6770` changed the behavior yet again again - unintentionally as this use case was never covered. + the :meth:`_dml.Insert.values` method would still fetch autoincrement + values up until 1.4.21 where :ticket:`6770` changed the behavior yet again + again unintentionally as this use case was never covered. The behavior is now defined as "working" to suit the case where databases such as SQLite, MySQL and MariaDB will ignore an explicit NULL primary key diff --git a/doc/build/conf.py b/doc/build/conf.py index 46fd6147f0..7f47d75406 100644 --- a/doc/build/conf.py +++ b/doc/build/conf.py @@ -28,7 +28,7 @@ os.environ["DISABLE_SQLALCHEMY_CEXT_RUNTIME"] = "true" # -- General configuration -------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = "3.5.0" +needs_sphinx = "5.0.1" # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. @@ -112,7 +112,15 @@ changelog_render_changeset = "https://www.sqlalchemy.org/trac/changeset/%s" exclude_patterns = ["build", "**/unreleased*/*", "**/*_include.rst"] autodoc_class_signature = "separated" -autodoc_typehints_format = "short" + + +# to use this, we need: +# 1. fix sphinx-paramlinks to work with "description" typing +# 2. we need a huge autodoc_type_aliases map as we have extensive type aliasing +# present, and typing is largely not very legible w/ the aliases +# autodoc_typehints = "description" +# autodoc_typehints_format = "short" +# autodoc_typehints_description_target = "documented" # zzzeeksphinx makes these conversions when it is rendering the # docstrings classes, methods, and functions within the scope of diff --git a/doc/build/core/engines.rst b/doc/build/core/engines.rst index e92d010ced..6edd01090a 100644 --- a/doc/build/core/engines.rst +++ b/doc/build/core/engines.rst @@ -204,68 +204,6 @@ Engine Creation API .. autoclass:: sqlalchemy.engine.URL :members: - .. py:attribute:: drivername - :annotation: str - - database backend and driver name, such as - ``postgresql+psycopg2`` - - .. py:attribute:: username - :annotation: str - - username string - - .. py:attribute:: password - :annotation: str - - password, which is normally a string but may also be any - object that has a ``__str__()`` method. - - .. py:attribute:: host - :annotation: str - - string hostname - - .. py:attribute:: port - :annotation: int - - integer port number - - .. py:attribute:: database - :annotation: str - - string database name - - .. py:attribute:: query - :annotation: Mapping[str, Union[str, Sequence[str]]] - - an immutable mapping representing the query string. contains strings - for keys and either strings or tuples of strings for values, e.g.:: - - >>> from sqlalchemy.engine import make_url - >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt") - >>> url.query - immutabledict({'alt_host': ('host1', 'host2'), 'ssl_cipher': '/path/to/crt'}) - - To create a mutable copy of this mapping, use the ``dict`` constructor:: - - mutable_query_opts = dict(url.query) - - .. seealso:: - - :attr:`_engine.URL.normalized_query` - normalizes all values into sequences - for consistent processing - - Methods for altering the contents of :attr:`_engine.URL.query`: - - :meth:`_engine.URL.update_query_dict` - - :meth:`_engine.URL.update_query_string` - - :meth:`_engine.URL.update_query_pairs` - - :meth:`_engine.URL.difference_update_query` - Pooling ======= diff --git a/doc/build/core/index.rst b/doc/build/core/index.rst index fda2b65ed9..0c99aa5723 100644 --- a/doc/build/core/index.rst +++ b/doc/build/core/index.rst @@ -19,6 +19,6 @@ Language provides a schema-centric usage paradigm. future .. toctree:: - :hidden: + :hidden: tutorial \ No newline at end of file diff --git a/doc/build/core/metadata.rst b/doc/build/core/metadata.rst index 38edab642e..97b30c4934 100644 --- a/doc/build/core/metadata.rst +++ b/doc/build/core/metadata.rst @@ -557,10 +557,12 @@ Column, Table, MetaData API --------------------------- .. attribute:: sqlalchemy.schema.BLANK_SCHEMA + :noindex: Refers to :attr:`.SchemaConst.BLANK_SCHEMA`. .. attribute:: sqlalchemy.schema.RETAIN_SCHEMA + :noindex: Refers to :attr:`.SchemaConst.RETAIN_SCHEMA` diff --git a/doc/build/core/pooling.rst b/doc/build/core/pooling.rst index 0a7b8a6dda..a0e8157048 100644 --- a/doc/build/core/pooling.rst +++ b/doc/build/core/pooling.rst @@ -597,19 +597,16 @@ API Documentation - Available Pool Implementations .. autoclass:: sqlalchemy.pool.Pool - .. automethod:: __init__ .. automethod:: connect .. automethod:: dispose .. automethod:: recreate .. autoclass:: sqlalchemy.pool.QueuePool - .. automethod:: __init__ .. automethod:: connect .. autoclass:: SingletonThreadPool - .. automethod:: __init__ .. autoclass:: AssertionPool diff --git a/doc/build/dialects/mysql.rst b/doc/build/dialects/mysql.rst index 83aa30bcbd..fea4936426 100644 --- a/doc/build/dialects/mysql.rst +++ b/doc/build/dialects/mysql.rst @@ -77,7 +77,7 @@ construction arguments, are as follows: .. autoclass:: DOUBLE :members: __init__ - + :noindex: .. autoclass:: ENUM :members: __init__ diff --git a/doc/build/dialects/postgresql.rst b/doc/build/dialects/postgresql.rst index 5be5da7cd5..81f0a0c4e1 100644 --- a/doc/build/dialects/postgresql.rst +++ b/doc/build/dialects/postgresql.rst @@ -51,6 +51,7 @@ construction arguments, are as follows: .. autoclass:: DOUBLE_PRECISION :members: __init__ + :noindex: .. autoclass:: ENUM diff --git a/doc/build/errors.rst b/doc/build/errors.rst index d56f11e72b..64e30bf593 100644 --- a/doc/build/errors.rst +++ b/doc/build/errors.rst @@ -33,358 +33,6 @@ Within this section, the goal is to try to provide background on some of the most common runtime errors as well as programming time errors. -Legacy API Features -=================== - -.. the reason we need this section here distinct from the migration notes - is because this is actually an ArgumentError that's raised by select() - when the "legacy" and "future" mode styles are used together. - -.. _error_c9ae: - -select() construct created in "legacy" mode; keyword arguments, etc. --------------------------------------------------------------------- - -The :func:`_expression.select` construct has been updated as of SQLAlchemy -1.4 to support the newer calling style that will be standard in -:ref:`SQLAlchemy 2.0 `. For backwards compatibility in the -interim, the construct accepts arguments in both the "legacy" style as well -as the "new" style. - -The "new" style features that column and table expressions are passed -positionally to the :func:`_expression.select` construct only; any other -modifiers to the object must be passed using subsequent method chaining:: - - # this is the way to do it going forward - stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid) - -For comparison, a :func:`_expression.select` in legacy forms of SQLAlchemy, -before methods like :meth:`.Select.where` were even added, would like:: - - # this is how it was documented in original SQLAlchemy versions - # many years ago - stmt = select([table1.c.myid], whereclause=table1.c.myid == table2.c.otherid) - -Or even that the "whereclause" would be passed positionally:: - - # this is also how it was documented in original SQLAlchemy versions - # many years ago - stmt = select([table1.c.myid], table1.c.myid == table2.c.otherid) - -For some years now, the additional "whereclause" and other arguments that are -accepted have been removed from most narrative documentation, leading to a -calling style that is most familiar as the list of column arguments passed -as a list, but no further arguments:: - - # this is how it's been documented since around version 1.0 or so - stmt = select([table1.c.myid]).where(table1.c.myid == table2.c.otherid) - -The document at :ref:`migration_20_5284` describes this change in terms -of :ref:`2.0 Migration `. - -.. seealso:: - - :ref:`migration_20_5284` - - :ref:`migration_20_toplevel` - - - -.. _error_b8d9: - -The in SQLAlchemy 2.0 will no longer --------------------------------------------------------------------------------------------- - -SQLAlchemy 2.0 is expected to be a major shift for a wide variety of key -SQLAlchemy usage patterns in both the Core and ORM components. The goal -of this release is to make a slight readjustment in some of the most -fundamental assumptions of SQLAlchemy since its early beginnings, and -to deliver a newly streamlined usage model that is hoped to be significantly -more minimalist and consistent between the Core and ORM components, as well as -more capable. - -Introduced at :ref:`migration_20_toplevel`, the SQLAlchemy 2.0 project includes -a comprehensive future compatibility system that is to be integrated into the -1.4 series of SQLAlchemy, such that applications will have a clear, -unambiguous, and incremental upgrade path in order to migrate applications to -being fully 2.0 compatible. The :class:`.exc.RemovedIn20Warning` deprecation -warning is at the base of this system to provide guidance on what behaviors in -an existing codebase will need to be modified. An overview of how to enable -this warning is at :ref:`deprecation_20_mode`. - -.. seealso:: - - :ref:`migration_20_toplevel` - An overview of the upgrade process from - the 1.x series, as well as the current goals and progress of SQLAlchemy - 2.0. - - - :ref:`deprecation_20_mode` - specific guidelines on how to use - "2.0 deprecations mode" in SQLAlchemy 1.4. - -.. _error_cprf: -.. _caching_caveats: - -Object will not produce a cache key, Performance Implications --------------------------------------------------------------- - -SQLAlchemy as of version 1.4 includes a -:ref:`SQL compilation caching facility ` which will allow -Core and ORM SQL constructs to cache their stringified form, along with other -structural information used to fetch results from the statement, allowing the -relatively expensive string compilation process to be skipped when another -structurally equivalent construct is next used. This system -relies upon functionality that is implemented for all SQL constructs, including -objects such as :class:`_schema.Column`, -:func:`_sql.select`, and :class:`_types.TypeEngine` objects, to produce a -**cache key** which fully represents their state to the degree that it affects -the SQL compilation process. - -If the warnings in question refer to widely used objects such as -:class:`_schema.Column` objects, and are shown to be affecting the majority of -SQL constructs being emitted (using the estimation techniques described at -:ref:`sql_caching_logging`) such that caching is generally not enabled for an -application, this will negatively impact performance and can in some cases -effectively produce a **performance degradation** compared to prior SQLAlchemy -versions. The FAQ at :ref:`faq_new_caching` covers this in additional detail. - -Caching disables itself if there's any doubt -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Caching relies on being able to generate a cache key that accurately represents -the **complete structure** of a statement in a **consistent** fashion. If a particular -SQL construct (or type) does not have the appropriate directives in place which -allow it to generate a proper cache key, then caching cannot be safely enabled: - -* The cache key must represent the **complete structure**: If the usage of two - separate instances of that construct may result in different SQL being - rendered, caching the SQL against the first instance of the element using a - cache key that does not capture the distinct differences between the first and - second elements will result in incorrect SQL being cached and rendered for the - second instance. - -* The cache key must be **consistent**: If a construct represents state that - changes every time, such as a literal value, producing unique SQL for every - instance of it, this construct is also not safe to cache, as repeated use of - the construct will quickly fill up the statement cache with unique SQL strings - that will likely not be used again, defeating the purpose of the cache. - -For the above two reasons, SQLAlchemy's caching system is **extremely -conservative** about deciding to cache the SQL corresponding to an object. - -Assertion attributes for caching -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The warning is emitted based on the criteria below. For further detail on -each, see the section :ref:`faq_new_caching`. - -* The :class:`.Dialect` itself (i.e. the module that is specified by the - first part of the URL we pass to :func:`_sa.create_engine`, like - ``postgresql+psycopg2://``), must indicate it has been reviewed and tested - to support caching correctly, which is indicated by the - :attr:`.Dialect.supports_statement_cache` attribute being set to ``True``. - When using third party dialects, consult with the maintainers of the dialect - so that they may follow the :ref:`steps to ensure caching may be enabled - ` in their dialect and publish a new release. - -* Third party or user defined types that inherit from either - :class:`.TypeDecorator` or :class:`.UserDefinedType` must include the - :attr:`.ExternalType.cache_ok` attribute in their definition, including for - all derived subclasses, following the guidelines described in the docstring - for :attr:`.ExternalType.cache_ok`. As before, if these datatypes are - imported from third party libraries, consult with the maintainers of that - library so that they may provide the necessary changes to their library and - publish a new release. - -* Third party or user defined SQL constructs that subclass from classes such - as :class:`.ClauseElement`, :class:`_schema.Column`, :class:`_dml.Insert` - etc, including simple subclasses as well as those which are designed to - work with the :ref:`sqlalchemy.ext.compiler_toplevel`, should normally - include the :attr:`.HasCacheKey.inherit_cache` attribute set to ``True`` - or ``False`` based on the design of the construct, following the guidelines - described at :ref:`compilerext_caching`. - -.. seealso:: - - :ref:`sql_caching_logging` - background on observing cache behavior - and efficiency - - :ref:`faq_new_caching` - in the :ref:`faq_toplevel` section - -.. _error_xaj1: - -An alias is being generated automatically for raw clauseelement ----------------------------------------------------------------- - -.. versionadded:: 1.4.26 - -This deprecation warning refers to a very old and likely not well known pattern -that applies to the legacy :meth:`_orm.Query.join` method as well as the -:term:`2.0 style` :meth:`_sql.Select.join` method, where a join can be stated -in terms of a :func:`_orm.relationship` but the target is the -:class:`_schema.Table` or other Core selectable to which the class is mapped, -rather than an ORM entity such as a mapped class or :func:`_orm.aliased` -construct:: - - a1 = Address.__table__ - - q = s.query(User).\ - join(a1, User.addresses).\ - filter(Address.email_address == 'ed@foo.com').all() - - -The above pattern also allows an arbitrary selectable, such as -a Core :class:`_sql.Join` or :class:`_sql.Alias` object, -however there is no automatic adaptation of this element, meaning the -Core element would need to be referred towards directly:: - - a1 = Address.__table__.alias() - - q = s.query(User).\ - join(a1, User.addresses).\ - filter(a1.c.email_address == 'ed@foo.com').all() - -The correct way to specify a join target is always by using the mapped -class itself or an :class:`_orm.aliased` object, in the latter case using the -:meth:`_orm.PropComparator.of_type` modifier to set up an alias:: - - # normal join to relationship entity - q = s.query(User).\ - join(User.addresses).\ - filter(Address.email_address == 'ed@foo.com') - - # name Address target explicitly, not necessary but legal - q = s.query(User).\ - join(Address, User.addresses).\ - filter(Address.email_address == 'ed@foo.com') - -Join to an alias:: - - from sqlalchemy.orm import aliased - - a1 = aliased(Address) - - # of_type() form; recommended - q = s.query(User).\ - join(User.addresses.of_type(a1)).\ - filter(a1.email_address == 'ed@foo.com') - - # target, onclause form - q = s.query(User).\ - join(a1, User.addresses).\ - filter(a1.email_address == 'ed@foo.com') - - -.. _error_xaj2: - -An alias is being generated automatically due to overlapping tables -------------------------------------------------------------------- - -.. versionadded:: 1.4.26 - -This warning is typically generated when querying using the -:meth:`_sql.Select.join` method or the legacy :meth:`_orm.Query.join` method -with mappings that involve joined table inheritance. The issue is that when -joining between two joined inheritance models that share a common base table, a -proper SQL JOIN between the two entities cannot be formed without applying an -alias to one side or the other; SQLAlchemy applies an alias to the right side -of the join. For example given a joined inheritance mapping as:: - - class Employee(Base): - __tablename__ = 'employee' - id = Column(Integer, primary_key=True) - manager_id = Column(ForeignKey("manager.id")) - name = Column(String(50)) - type = Column(String(50)) - - reports_to = relationship("Manager", foreign_keys=manager_id) - - __mapper_args__ = { - 'polymorphic_identity':'employee', - 'polymorphic_on':type, - } - - class Manager(Employee): - __tablename__ = 'manager' - id = Column(Integer, ForeignKey('employee.id'), primary_key=True) - - __mapper_args__ = { - 'polymorphic_identity':'manager', - 'inherit_condition': id == Employee.id - } - -The above mapping includes a relationship between the ``Employee`` and -``Manager`` classes. Since both classes make use of the "employee" database -table, from a SQL perspective this is a -:ref:`self referential relationship `. If we wanted to -query from both the ``Employee`` and ``Manager`` models using a join, at the -SQL level the "employee" table needs to be included twice in the query, which -means it must be aliased. When we create such a join using the SQLAlchemy -ORM, we get SQL that looks like the following: - -.. sourcecode:: pycon+sql - - >>> stmt = select(Employee, Manager).join(Employee.reports_to) - >>> print(stmt) - {opensql}SELECT employee.id, employee.manager_id, employee.name, - employee.type, manager_1.id AS id_1, employee_1.id AS id_2, - employee_1.manager_id AS manager_id_1, employee_1.name AS name_1, - employee_1.type AS type_1 - FROM employee JOIN - (employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id) - ON manager_1.id = employee.manager_id - -Above, the SQL selects FROM the ``employee`` table, representing the -``Employee`` entity in the query. It then joins to a right-nested join of -``employee AS employee_1 JOIN manager AS manager_1``, where the ``employee`` -table is stated again, except as an anonymous alias ``employee_1``. This is the -"automatic generation of an alias" that the warning message refers towards. - -When SQLAlchemy loads ORM rows that each contain an ``Employee`` and a -``Manager`` object, the ORM must adapt rows from what above is the -``employee_1`` and ``manager_1`` table aliases into those of the un-aliased -``Manager`` class. This process is internally complex and does not accommodate -for all API features, notably when trying to use eager loading features such as -:func:`_orm.contains_eager` with more deeply nested queries than are shown -here. As the pattern is unreliable for more complex scenarios and involves -implicit decisionmaking that is difficult to anticipate and follow, -the warning is emitted and this pattern may be considered a legacy feature. The -better way to write this query is to use the same patterns that apply to any -other self-referential relationship, which is to use the :func:`_orm.aliased` -construct explicitly. For joined-inheritance and other join-oriented mappings, -it is usually desirable to add the use of the :paramref:`_orm.aliased.flat` -parameter, which will allow a JOIN of two or more tables to be aliased by -applying an alias to the individual tables within the join, rather than -embedding the join into a new subquery: - -.. sourcecode:: pycon+sql - - >>> from sqlalchemy.orm import aliased - >>> manager_alias = aliased(Manager, flat=True) - >>> stmt = select(Employee, manager_alias).join(Employee.reports_to.of_type(manager_alias)) - >>> print(stmt) - {opensql}SELECT employee.id, employee.manager_id, employee.name, - employee.type, manager_1.id AS id_1, employee_1.id AS id_2, - employee_1.manager_id AS manager_id_1, employee_1.name AS name_1, - employee_1.type AS type_1 - FROM employee JOIN - (employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id) - ON manager_1.id = employee.manager_id - -If we then wanted to use :func:`_orm.contains_eager` to populate the -``reports_to`` attribute, we refer to the alias:: - - >>> stmt =select(Employee).join( - ... Employee.reports_to.of_type(manager_alias) - ... ).options( - ... contains_eager(Employee.reports_to.of_type(manager_alias)) - ... ) - -Without using the explicit :func:`_orm.aliased` object, in some more nested -cases the :func:`_orm.contains_eager` option does not have enough context to -know where to get its data from, in the case that the ORM is "auto-aliasing" -in a very nested context. Therefore it's best not to rely on this feature -and instead keep the SQL construction as explicit as possible. Connections and Transactions ============================ @@ -684,6 +332,95 @@ the database driver (DBAPI), not SQLAlchemy itself. SQL Expression Language ======================= +.. _error_cprf: +.. _caching_caveats: + +Object will not produce a cache key, Performance Implications +-------------------------------------------------------------- + +SQLAlchemy as of version 1.4 includes a +:ref:`SQL compilation caching facility ` which will allow +Core and ORM SQL constructs to cache their stringified form, along with other +structural information used to fetch results from the statement, allowing the +relatively expensive string compilation process to be skipped when another +structurally equivalent construct is next used. This system +relies upon functionality that is implemented for all SQL constructs, including +objects such as :class:`_schema.Column`, +:func:`_sql.select`, and :class:`_types.TypeEngine` objects, to produce a +**cache key** which fully represents their state to the degree that it affects +the SQL compilation process. + +If the warnings in question refer to widely used objects such as +:class:`_schema.Column` objects, and are shown to be affecting the majority of +SQL constructs being emitted (using the estimation techniques described at +:ref:`sql_caching_logging`) such that caching is generally not enabled for an +application, this will negatively impact performance and can in some cases +effectively produce a **performance degradation** compared to prior SQLAlchemy +versions. The FAQ at :ref:`faq_new_caching` covers this in additional detail. + +Caching disables itself if there's any doubt +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Caching relies on being able to generate a cache key that accurately represents +the **complete structure** of a statement in a **consistent** fashion. If a particular +SQL construct (or type) does not have the appropriate directives in place which +allow it to generate a proper cache key, then caching cannot be safely enabled: + +* The cache key must represent the **complete structure**: If the usage of two + separate instances of that construct may result in different SQL being + rendered, caching the SQL against the first instance of the element using a + cache key that does not capture the distinct differences between the first and + second elements will result in incorrect SQL being cached and rendered for the + second instance. + +* The cache key must be **consistent**: If a construct represents state that + changes every time, such as a literal value, producing unique SQL for every + instance of it, this construct is also not safe to cache, as repeated use of + the construct will quickly fill up the statement cache with unique SQL strings + that will likely not be used again, defeating the purpose of the cache. + +For the above two reasons, SQLAlchemy's caching system is **extremely +conservative** about deciding to cache the SQL corresponding to an object. + +Assertion attributes for caching +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The warning is emitted based on the criteria below. For further detail on +each, see the section :ref:`faq_new_caching`. + +* The :class:`.Dialect` itself (i.e. the module that is specified by the + first part of the URL we pass to :func:`_sa.create_engine`, like + ``postgresql+psycopg2://``), must indicate it has been reviewed and tested + to support caching correctly, which is indicated by the + :attr:`.Dialect.supports_statement_cache` attribute being set to ``True``. + When using third party dialects, consult with the maintainers of the dialect + so that they may follow the :ref:`steps to ensure caching may be enabled + ` in their dialect and publish a new release. + +* Third party or user defined types that inherit from either + :class:`.TypeDecorator` or :class:`.UserDefinedType` must include the + :attr:`.ExternalType.cache_ok` attribute in their definition, including for + all derived subclasses, following the guidelines described in the docstring + for :attr:`.ExternalType.cache_ok`. As before, if these datatypes are + imported from third party libraries, consult with the maintainers of that + library so that they may provide the necessary changes to their library and + publish a new release. + +* Third party or user defined SQL constructs that subclass from classes such + as :class:`.ClauseElement`, :class:`_schema.Column`, :class:`_dml.Insert` + etc, including simple subclasses as well as those which are designed to + work with the :ref:`sqlalchemy.ext.compiler_toplevel`, should normally + include the :attr:`.HasCacheKey.inherit_cache` attribute set to ``True`` + or ``False`` based on the design of the construct, following the guidelines + described at :ref:`compilerext_caching`. + +.. seealso:: + + :ref:`sql_caching_logging` - background on observing cache behavior + and efficiency + + :ref:`faq_new_caching` - in the :ref:`faq_toplevel` section + .. _error_l7de: @@ -799,46 +536,6 @@ The solution is to access the :class:`_schema.Column` directly using the CheckConstraint(cprop.expression > 5), ) -.. _error_2afi: - -This Compiled object is not bound to any Engine or Connection -------------------------------------------------------------- - -This error refers to the concept of "bound metadata", described at -:ref:`dbengine_implicit`. The issue occurs when one invokes the -:meth:`.Executable.execute` method directly off of a Core expression object -that is not associated with any :class:`_engine.Engine`:: - - metadata_obj = MetaData() - table = Table('t', metadata_obj, Column('q', Integer)) - - stmt = select(table) - result = stmt.execute() # <--- raises - -What the logic is expecting is that the :class:`_schema.MetaData` object has -been **bound** to a :class:`_engine.Engine`:: - - engine = create_engine("mysql+pymysql://user:pass@host/db") - metadata_obj = MetaData(bind=engine) - -Where above, any statement that derives from a :class:`_schema.Table` which -in turn derives from that :class:`_schema.MetaData` will implicitly make use of -the given :class:`_engine.Engine` in order to invoke the statement. - -Note that the concept of bound metadata is a **legacy pattern** and in most -cases is **highly discouraged**. The best way to invoke the statement is -to pass it to the :meth:`_engine.Connection.execute` method of a :class:`_engine.Connection`:: - - with engine.connect() as conn: - result = conn.execute(stmt) - -When using the ORM, a similar facility is available via the :class:`.Session`:: - - result = session.execute(stmt) - -.. seealso:: - - :ref:`dbengine_implicit` .. _error_cd3x: @@ -906,9 +603,7 @@ Since "b" is required, pass it as ``None`` so that the INSERT may proceed:: .. seealso:: - :ref:`coretutorial_bind_param` - - :ref:`execute_multiple` + :ref:`tutorial_sending_parameters` .. _error_89ve: @@ -937,34 +632,209 @@ Above, ``stmt`` represents a SELECT statement. The error is produced when we wa to use ``stmt`` directly as a FROM clause in another SELECT, such as if we attempted to select from it:: - new_stmt_1 = select(stmt) + new_stmt_1 = select(stmt) + +Or if we wanted to use it in a FROM clause such as in a JOIN:: + + new_stmt_2 = select(some_table).select_from(some_table.join(stmt)) + +In previous versions of SQLAlchemy, using a SELECT inside of another SELECT +would produce a parenthesized, unnamed subquery. In most cases, this form of +SQL is not very useful as databases like MySQL and PostgreSQL require that +subqueries in FROM clauses have named aliases, which means using the +:meth:`_expression.SelectBase.alias` method or as of 1.4 using the +:meth:`_expression.SelectBase.subquery` method to produce this. On other databases, it +is still much clearer for the subquery to have a name to resolve any ambiguity +on future references to column names inside the subquery. + +Beyond the above practical reasons, there are a lot of other SQLAlchemy-oriented +reasons the change is being made. The correct form of the above two statements +therefore requires that :meth:`_expression.SelectBase.subquery` is used:: + + subq = stmt.subquery() + + new_stmt_1 = select(subq) + + new_stmt_2 = select(some_table).select_from(some_table.join(subq)) + +.. seealso:: + + :ref:`change_4617` + +.. _error_xaj1: + +An alias is being generated automatically for raw clauseelement +---------------------------------------------------------------- + +.. versionadded:: 1.4.26 + +This deprecation warning refers to a very old and likely not well known pattern +that applies to the legacy :meth:`_orm.Query.join` method as well as the +:term:`2.0 style` :meth:`_sql.Select.join` method, where a join can be stated +in terms of a :func:`_orm.relationship` but the target is the +:class:`_schema.Table` or other Core selectable to which the class is mapped, +rather than an ORM entity such as a mapped class or :func:`_orm.aliased` +construct:: + + a1 = Address.__table__ + + q = s.query(User).\ + join(a1, User.addresses).\ + filter(Address.email_address == 'ed@foo.com').all() + + +The above pattern also allows an arbitrary selectable, such as +a Core :class:`_sql.Join` or :class:`_sql.Alias` object, +however there is no automatic adaptation of this element, meaning the +Core element would need to be referred towards directly:: + + a1 = Address.__table__.alias() + + q = s.query(User).\ + join(a1, User.addresses).\ + filter(a1.c.email_address == 'ed@foo.com').all() + +The correct way to specify a join target is always by using the mapped +class itself or an :class:`_orm.aliased` object, in the latter case using the +:meth:`_orm.PropComparator.of_type` modifier to set up an alias:: + + # normal join to relationship entity + q = s.query(User).\ + join(User.addresses).\ + filter(Address.email_address == 'ed@foo.com') + + # name Address target explicitly, not necessary but legal + q = s.query(User).\ + join(Address, User.addresses).\ + filter(Address.email_address == 'ed@foo.com') + +Join to an alias:: + + from sqlalchemy.orm import aliased + + a1 = aliased(Address) + + # of_type() form; recommended + q = s.query(User).\ + join(User.addresses.of_type(a1)).\ + filter(a1.email_address == 'ed@foo.com') + + # target, onclause form + q = s.query(User).\ + join(a1, User.addresses).\ + filter(a1.email_address == 'ed@foo.com') + + +.. _error_xaj2: + +An alias is being generated automatically due to overlapping tables +------------------------------------------------------------------- + +.. versionadded:: 1.4.26 + +This warning is typically generated when querying using the +:meth:`_sql.Select.join` method or the legacy :meth:`_orm.Query.join` method +with mappings that involve joined table inheritance. The issue is that when +joining between two joined inheritance models that share a common base table, a +proper SQL JOIN between the two entities cannot be formed without applying an +alias to one side or the other; SQLAlchemy applies an alias to the right side +of the join. For example given a joined inheritance mapping as:: + + class Employee(Base): + __tablename__ = 'employee' + id = Column(Integer, primary_key=True) + manager_id = Column(ForeignKey("manager.id")) + name = Column(String(50)) + type = Column(String(50)) + + reports_to = relationship("Manager", foreign_keys=manager_id) + + __mapper_args__ = { + 'polymorphic_identity':'employee', + 'polymorphic_on':type, + } + + class Manager(Employee): + __tablename__ = 'manager' + id = Column(Integer, ForeignKey('employee.id'), primary_key=True) + + __mapper_args__ = { + 'polymorphic_identity':'manager', + 'inherit_condition': id == Employee.id + } -Or if we wanted to use it in a FROM clause such as in a JOIN:: +The above mapping includes a relationship between the ``Employee`` and +``Manager`` classes. Since both classes make use of the "employee" database +table, from a SQL perspective this is a +:ref:`self referential relationship `. If we wanted to +query from both the ``Employee`` and ``Manager`` models using a join, at the +SQL level the "employee" table needs to be included twice in the query, which +means it must be aliased. When we create such a join using the SQLAlchemy +ORM, we get SQL that looks like the following: - new_stmt_2 = select(some_table).select_from(some_table.join(stmt)) +.. sourcecode:: pycon+sql -In previous versions of SQLAlchemy, using a SELECT inside of another SELECT -would produce a parenthesized, unnamed subquery. In most cases, this form of -SQL is not very useful as databases like MySQL and PostgreSQL require that -subqueries in FROM clauses have named aliases, which means using the -:meth:`_expression.SelectBase.alias` method or as of 1.4 using the -:meth:`_expression.SelectBase.subquery` method to produce this. On other databases, it -is still much clearer for the subquery to have a name to resolve any ambiguity -on future references to column names inside the subquery. + >>> stmt = select(Employee, Manager).join(Employee.reports_to) + >>> print(stmt) + {opensql}SELECT employee.id, employee.manager_id, employee.name, + employee.type, manager_1.id AS id_1, employee_1.id AS id_2, + employee_1.manager_id AS manager_id_1, employee_1.name AS name_1, + employee_1.type AS type_1 + FROM employee JOIN + (employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id) + ON manager_1.id = employee.manager_id -Beyond the above practical reasons, there are a lot of other SQLAlchemy-oriented -reasons the change is being made. The correct form of the above two statements -therefore requires that :meth:`_expression.SelectBase.subquery` is used:: +Above, the SQL selects FROM the ``employee`` table, representing the +``Employee`` entity in the query. It then joins to a right-nested join of +``employee AS employee_1 JOIN manager AS manager_1``, where the ``employee`` +table is stated again, except as an anonymous alias ``employee_1``. This is the +"automatic generation of an alias" that the warning message refers towards. - subq = stmt.subquery() +When SQLAlchemy loads ORM rows that each contain an ``Employee`` and a +``Manager`` object, the ORM must adapt rows from what above is the +``employee_1`` and ``manager_1`` table aliases into those of the un-aliased +``Manager`` class. This process is internally complex and does not accommodate +for all API features, notably when trying to use eager loading features such as +:func:`_orm.contains_eager` with more deeply nested queries than are shown +here. As the pattern is unreliable for more complex scenarios and involves +implicit decisionmaking that is difficult to anticipate and follow, +the warning is emitted and this pattern may be considered a legacy feature. The +better way to write this query is to use the same patterns that apply to any +other self-referential relationship, which is to use the :func:`_orm.aliased` +construct explicitly. For joined-inheritance and other join-oriented mappings, +it is usually desirable to add the use of the :paramref:`_orm.aliased.flat` +parameter, which will allow a JOIN of two or more tables to be aliased by +applying an alias to the individual tables within the join, rather than +embedding the join into a new subquery: - new_stmt_1 = select(subq) +.. sourcecode:: pycon+sql - new_stmt_2 = select(some_table).select_from(some_table.join(subq)) + >>> from sqlalchemy.orm import aliased + >>> manager_alias = aliased(Manager, flat=True) + >>> stmt = select(Employee, manager_alias).join(Employee.reports_to.of_type(manager_alias)) + >>> print(stmt) + {opensql}SELECT employee.id, employee.manager_id, employee.name, + employee.type, manager_1.id AS id_1, employee_1.id AS id_2, + employee_1.manager_id AS manager_id_1, employee_1.name AS name_1, + employee_1.type AS type_1 + FROM employee JOIN + (employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id) + ON manager_1.id = employee.manager_id -.. seealso:: +If we then wanted to use :func:`_orm.contains_eager` to populate the +``reports_to`` attribute, we refer to the alias:: - :ref:`change_4617` + >>> stmt =select(Employee).join( + ... Employee.reports_to.of_type(manager_alias) + ... ).options( + ... contains_eager(Employee.reports_to.of_type(manager_alias)) + ... ) + +Without using the explicit :func:`_orm.aliased` object, in some more nested +cases the :func:`_orm.contains_eager` option does not have enough context to +know where to get its data from, in the case that the ORM is "auto-aliasing" +in a very nested context. Therefore it's best not to rely on this feature +and instead keep the SQL construction as explicit as possible. Object Relational Mapping @@ -1531,24 +1401,134 @@ Legacy Exceptions Exceptions in this section are not generated by current SQLAlchemy versions, however are provided here to suit exception message hyperlinks. +.. _error_b8d9: + +The in SQLAlchemy 2.0 will no longer +-------------------------------------------------------------------------------------------- + +SQLAlchemy 2.0 represents a major shift for a wide variety of key +SQLAlchemy usage patterns in both the Core and ORM components. The goal +of the 2.0 release is to make a slight readjustment in some of the most +fundamental assumptions of SQLAlchemy since its early beginnings, and +to deliver a newly streamlined usage model that is hoped to be significantly +more minimalist and consistent between the Core and ORM components, as well as +more capable. + +Introduced at :ref:`migration_20_toplevel`, the SQLAlchemy 2.0 project includes +a comprehensive future compatibility system that's integrated into the +1.4 series of SQLAlchemy, such that applications will have a clear, +unambiguous, and incremental upgrade path in order to migrate applications to +being fully 2.0 compatible. The :class:`.exc.RemovedIn20Warning` deprecation +warning is at the base of this system to provide guidance on what behaviors in +an existing codebase will need to be modified. An overview of how to enable +this warning is at :ref:`deprecation_20_mode`. + +.. seealso:: + + :ref:`migration_20_toplevel` - An overview of the upgrade process from + the 1.x series, as well as the current goals and progress of SQLAlchemy + 2.0. + + + :ref:`deprecation_20_mode` - specific guidelines on how to use + "2.0 deprecations mode" in SQLAlchemy 1.4. + + +.. _error_s9r1: + +Object is being merged into a Session along the backref cascade +--------------------------------------------------------------- + +This message refers to the "backref cascade" behavior of SQLAlchemy, +removed in version 2.0. This refers to the action of +an object being added into a :class:`_orm.Session` as a result of another +object that's already present in that session being associated with it. +As this behavior has been shown to be more confusing than helpful, +the :paramref:`_orm.relationship.cascade_backrefs` and +:paramref:`_orm.backref.cascade_backrefs` parameters were added, which can +be set to ``False`` to disable it, and in SQLAlchemy 2.0 the "cascade backrefs" +behavior has been removed entirely. + +For older SQLAlchemy versions, to set +:paramref:`_orm.relationship.cascade_backrefs` to ``False`` on a backref that +is currently configured using the :paramref:`_orm.relationship.backref` string +parameter, the backref must be declared using the :func:`_orm.backref` function +first so that the :paramref:`_orm.backref.cascade_backrefs` parameter may be +passed. + +Alternatively, the entire "cascade backrefs" behavior can be turned off +across the board by using the :class:`_orm.Session` in "future" mode, +by passing ``True`` for the :paramref:`_orm.Session.future` parameter. + +.. seealso:: + + :ref:`change_5150` - background on the change for SQLAlchemy 2.0. + + +.. _error_c9ae: + +select() construct created in "legacy" mode; keyword arguments, etc. +-------------------------------------------------------------------- + +The :func:`_expression.select` construct has been updated as of SQLAlchemy +1.4 to support the newer calling style that is standard in +SQLAlchemy 2.0. For backwards compatibility within +the 1.4 series, the construct accepts arguments in both the "legacy" style as well +as the "new" style. + +The "new" style features that column and table expressions are passed +positionally to the :func:`_expression.select` construct only; any other +modifiers to the object must be passed using subsequent method chaining:: + + # this is the way to do it going forward + stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid) + +For comparison, a :func:`_expression.select` in legacy forms of SQLAlchemy, +before methods like :meth:`.Select.where` were even added, would like:: + + # this is how it was documented in original SQLAlchemy versions + # many years ago + stmt = select([table1.c.myid], whereclause=table1.c.myid == table2.c.otherid) + +Or even that the "whereclause" would be passed positionally:: + + # this is also how it was documented in original SQLAlchemy versions + # many years ago + stmt = select([table1.c.myid], table1.c.myid == table2.c.otherid) + +For some years now, the additional "whereclause" and other arguments that are +accepted have been removed from most narrative documentation, leading to a +calling style that is most familiar as the list of column arguments passed +as a list, but no further arguments:: + + # this is how it's been documented since around version 1.0 or so + stmt = select([table1.c.myid]).where(table1.c.myid == table2.c.otherid) + +The document at :ref:`migration_20_5284` describes this change in terms +of :ref:`2.0 Migration `. + +.. seealso:: + + :ref:`migration_20_5284` + + :ref:`migration_20_toplevel` .. _error_c9bf: A bind was located via legacy bound metadata, but since future=True is set on this Session, this bind is ignored. ------------------------------------------------------------------------------------------------------------------- -.. note:: This is a legacy error message that is not in the 2.x series of - SQLAlchemy. +The concept of "bound metadata" is present up until SQLAlchemy 1.4; as +of SQLAlchemy 2.0 it's been removed. -The concept of "bound metadata" is being removed in SQLAlchemy 2.0. This -refers to the :paramref:`_schema.MetaData.bind` parameter on the +This error refers to the :paramref:`_schema.MetaData.bind` parameter on the :class:`_schema.MetaData` object that in turn allows objects like the ORM :class:`_orm.Session` to associate a particular mapped class with an -:class:`_orm.Engine`. In SQLAlchemy 2.0, the :class:`_orm.Session` must be +:class:`_orm.Engine`. In SQLAlchemy 2.0, the :class:`_orm.Session` must be linked to each :class:`_orm.Engine` directly. That is, instead of instantiating -the :class:`_orm.Session` or -:class:`_orm.sessionmaker` without any arguments, and associating the -:class:`_engine.Engine` with the :class:`_schema.MetaData`:: +the :class:`_orm.Session` or :class:`_orm.sessionmaker` without any arguments, +and associating the :class:`_engine.Engine` with the +:class:`_schema.MetaData`:: engine = create_engine("sqlite://") Session = sessionmaker() @@ -1585,33 +1565,99 @@ In SQLAlchemy 1.4, this :term:`2.0 style` behavior is enabled when the :paramref:`_orm.Session.future` flag is set on :class:`_orm.sessionmaker` or :class:`_orm.Session`. -.. _error_s9r1: -Object is being merged into a Session along the backref cascade ---------------------------------------------------------------- +.. _error_2afi: -This message refers to the "backref cascade" behavior of SQLAlchemy, -removed in version 2.0. This refers to the action of -an object being added into a :class:`_orm.Session` as a result of another -object that's already present in that session being associated with it. -As this behavior has been shown to be more confusing than helpful, -the :paramref:`_orm.relationship.cascade_backrefs` and -:paramref:`_orm.backref.cascade_backrefs` parameters were added, which can -be set to ``False`` to disable it, and in SQLAlchemy 2.0 the "cascade backrefs" -behavior has been removed entirely. +This Compiled object is not bound to any Engine or Connection +------------------------------------------------------------- -For older SQLAlchemy versions, to set -:paramref:`_orm.relationship.cascade_backrefs` to ``False`` on a backref that -is currently configured using the :paramref:`_orm.relationship.backref` string -parameter, the backref must be declared using the :func:`_orm.backref` function -first so that the :paramref:`_orm.backref.cascade_backrefs` parameter may be -passed. +This error refers to the concept of "bound metadata", which is a legacy +SQLAlchemy pattern present only in 1.x versions. The issue occurs when one invokes +the :meth:`.Executable.execute` method directly off of a Core expression object +that is not associated with any :class:`_engine.Engine`:: -Alternatively, the entire "cascade backrefs" behavior can be turned off -across the board by using the :class:`_orm.Session` in "future" mode, -by passing ``True`` for the :paramref:`_orm.Session.future` parameter. + metadata_obj = MetaData() + table = Table('t', metadata_obj, Column('q', Integer)) + + stmt = select(table) + result = stmt.execute() # <--- raises + +What the logic is expecting is that the :class:`_schema.MetaData` object has +been **bound** to a :class:`_engine.Engine`:: + + engine = create_engine("mysql+pymysql://user:pass@host/db") + metadata_obj = MetaData(bind=engine) + +Where above, any statement that derives from a :class:`_schema.Table` which +in turn derives from that :class:`_schema.MetaData` will implicitly make use of +the given :class:`_engine.Engine` in order to invoke the statement. + +Note that the concept of bound metadata is **not present in SQLAlchemy 2.0**. +The correct way to invoke statements is via +the :meth:`_engine.Connection.execute` method of a :class:`_engine.Connection`:: + + with engine.connect() as conn: + result = conn.execute(stmt) + +When using the ORM, a similar facility is available via the :class:`.Session`:: + + result = session.execute(stmt) .. seealso:: - :ref:`change_5150` - background on the change for SQLAlchemy 2.0. + :ref:`tutorial_statement_execution` + +.. _error_8s2a: + +This connection is on an inactive transaction. Please rollback() fully before proceeding +------------------------------------------------------------------------------------------ + +This error condition was added to SQLAlchemy as of version 1.4, and does not +apply to SQLAlchemy 2.0. The error +refers to the state where a :class:`_engine.Connection` is placed into a +transaction using a method like :meth:`_engine.Connection.begin`, and then a +further "marker" transaction is created within that scope; the "marker" +transaction is then rolled back using :meth:`.Transaction.rollback` or closed +using :meth:`.Transaction.close`, however the outer transaction is still +present in an "inactive" state and must be rolled back. + +The pattern looks like:: + + engine = create_engine(...) + + connection = engine.connect() + transaction1 = connection.begin() + + # this is a "sub" or "marker" transaction, a logical nesting + # structure based on "real" transaction transaction1 + transaction2 = connection.begin() + transaction2.rollback() + + # transaction1 is still present and needs explicit rollback, + # so this will raise + connection.execute(text("select 1")) + +Above, ``transaction2`` is a "marker" transaction, which indicates a logical +nesting of transactions within an outer one; while the inner transaction +can roll back the whole transaction via its rollback() method, its commit() +method has no effect except to close the scope of the "marker" transaction +itself. The call to ``transaction2.rollback()`` has the effect of +**deactivating** transaction1 which means it is essentially rolled back +at the database level, however is still present in order to accommodate +a consistent nesting pattern of transactions. + +The correct resolution is to ensure the outer transaction is also +rolled back:: + + transaction1.rollback() + +This pattern is not commonly used in Core. Within the ORM, a similar issue can +occur which is the product of the ORM's "logical" transaction structure; this +is described in the FAQ entry at :ref:`faq_session_rollback`. + +The "subtransaction" pattern is removed in SQLAlchemy 2.0 so that this +particular programming pattern is no longer be available, preventing +this error message. + + diff --git a/doc/build/faq/connections.rst b/doc/build/faq/connections.rst index 9450dfdfa0..4a21318f40 100644 --- a/doc/build/faq/connections.rst +++ b/doc/build/faq/connections.rst @@ -167,18 +167,12 @@ a new transaction when it is first used that remains in effect for subsequent statements, until the DBAPI-level ``connection.commit()`` or ``connection.rollback()`` method is invoked. -As discussed at :ref:`autocommit`, there is a library level "autocommit" -feature which is deprecated in 1.4 that causes :term:`DML` and :term:`DDL` -executions to commit automatically after individual statements are executed; -however, outside of this deprecated case, modern use of SQLAlchemy works with -this transaction in all cases and does not commit any data unless explicitly -told to commit. - -At the ORM level, a similar situation where the ORM -:class:`_orm.Session` object also presents a legacy "autocommit" operation is -present; however even if this legacy mode of operation is used, the -:class:`_orm.Session` still makes use of transactions internally, -particularly within the :meth:`_orm.Session.flush` process. +In modern use of SQLAlchemy, a series of SQL statements are always invoked +within this transactional state, assuming +:ref:`DBAPI autocommit mode ` is not enabled (more on that in +the next section), meaning that no single statement is automatically committed; +if an operation fails, the effects of all statements within the current +transaction will be lost. The implication that this has for the notion of "retrying" a statement is that in the default case, when a connection is lost, **the entire transaction is @@ -188,9 +182,10 @@ SQLAlchemy does not have a transparent "reconnection" feature that works mid-transaction, for the case when the database connection has disconnected while being used. The canonical approach to dealing with mid-operation disconnects is to **retry the entire operation from the start of the -transaction**, often by using a Python "retry" decorator, or to otherwise +transaction**, often by using a custom Python decorator that will +"retry" a particular function several times until it succeeds, or to otherwise architect the application in such a way that it is resilient against -transactions that are dropped. +transactions that are dropped that then cause operations to fail. There is also the notion of extensions that can keep track of all of the statements that have proceeded within a transaction and then replay them all in diff --git a/doc/build/faq/sessions.rst b/doc/build/faq/sessions.rst index 8281b4bf55..0f9f7575b8 100644 --- a/doc/build/faq/sessions.rst +++ b/doc/build/faq/sessions.rst @@ -350,7 +350,7 @@ How Do I use Textual SQL with ORM Queries? See: -* :ref:`orm_tutorial_literal_sql` - Ad-hoc textual blocks with :class:`_query.Query` +* :ref:`orm_queryguide_selecting_text` - Ad-hoc textual blocks with :class:`_query.Query` * :ref:`session_sql_expressions` - Using :class:`.Session` with textual SQL directly. diff --git a/doc/build/glossary.rst b/doc/build/glossary.rst index a54d7715e5..3d7ebeb880 100644 --- a/doc/build/glossary.rst +++ b/doc/build/glossary.rst @@ -110,7 +110,7 @@ Glossary `bind parameters `_ - at Use The Index, Luke! - + :ref:`tutorial_sending_parameters` - in the :ref:`unified_tutorial` selectable A term used in SQLAlchemy to describe a SQL construct that represents diff --git a/doc/build/orm/extensions/associationproxy.rst b/doc/build/orm/extensions/associationproxy.rst index 2f26517b26..5e76e5bf78 100644 --- a/doc/build/orm/extensions/associationproxy.rst +++ b/doc/build/orm/extensions/associationproxy.rst @@ -606,3 +606,4 @@ API Documentation :inherited-members: .. autoclass:: AssociationProxyExtensionType + :members: \ No newline at end of file diff --git a/doc/build/orm/extensions/baked.rst b/doc/build/orm/extensions/baked.rst index f22e28fa5a..b3c21716a2 100644 --- a/doc/build/orm/extensions/baked.rst +++ b/doc/build/orm/extensions/baked.rst @@ -475,4 +475,5 @@ API Documentation .. autoclass:: Result :members: + :noindex: diff --git a/doc/build/orm/extensions/hybrid.rst b/doc/build/orm/extensions/hybrid.rst index 571aca7222..d403c196ff 100644 --- a/doc/build/orm/extensions/hybrid.rst +++ b/doc/build/orm/extensions/hybrid.rst @@ -18,3 +18,4 @@ API Reference .. autoclass:: HybridExtensionType + :members: \ No newline at end of file diff --git a/doc/build/orm/extensions/mypy.rst b/doc/build/orm/extensions/mypy.rst index 2fe2333113..c1fcbc747b 100644 --- a/doc/build/orm/extensions/mypy.rst +++ b/doc/build/orm/extensions/mypy.rst @@ -4,7 +4,7 @@ Mypy / Pep-484 Support for ORM Mappings ======================================== Support for :pep:`484` typing annotations as well as the -`Mypy `_ type checking tool. +MyPy_ type checking tool. .. topic:: SQLAlchemy Mypy Plugin Status Update @@ -62,7 +62,7 @@ TODO: document uninstallation of existing stubs: SQLAlchemy 2.0 is expected to be directly typed. -The `Mypy `_ package itself is a dependency. +The Mypy_ package itself is a dependency. Both packages may be installed using the "mypy" extras hook using pip:: @@ -597,3 +597,5 @@ With the above recipe, the attributes listed in ``_mypy_mapped_attrs`` will be applied with the :class:`_orm.Mapped` typing information so that the ``User`` class will behave as a SQLAlchemy mapped class when used in a class-bound context. + +.. _Mypy: https://mypy.readthedocs.io/ diff --git a/doc/build/orm/index.rst b/doc/build/orm/index.rst index 853f72cf56..997f0dd6ab 100644 --- a/doc/build/orm/index.rst +++ b/doc/build/orm/index.rst @@ -21,6 +21,6 @@ tutorial. examples .. toctree:: - :hidden: + :hidden: tutorial diff --git a/doc/build/orm/internals.rst b/doc/build/orm/internals.rst index ee00642711..b2551bbfae 100644 --- a/doc/build/orm/internals.rst +++ b/doc/build/orm/internals.rst @@ -88,6 +88,7 @@ sections, are listed here. :attr:`.SchemaItem.info` .. autoclass:: InspectionAttrExtensionType + :members: .. autoclass:: NotExtension :members: diff --git a/doc/build/orm/persistence_techniques.rst b/doc/build/orm/persistence_techniques.rst index 8d18ac7ceb..cd83f7fc8b 100644 --- a/doc/build/orm/persistence_techniques.rst +++ b/doc/build/orm/persistence_techniques.rst @@ -988,7 +988,7 @@ Comparison to Core Insert / Update Constructs The bulk methods offer performance that under particular circumstances can be close to that of using the core :class:`_expression.Insert` and :class:`_expression.Update` constructs in an "executemany" context (for a description -of "executemany", see :ref:`execute_multiple` in the Core tutorial). +of "executemany", see :ref:`tutorial_multiple_parameters` in the Core tutorial). In order to achieve this, the :paramref:`.Session.bulk_insert_mappings.return_defaults` flag should be disabled so that rows can be batched together. The example diff --git a/doc/build/orm/queryguide.rst b/doc/build/orm/queryguide.rst index f184c4c446..f4e2a0ac68 100644 --- a/doc/build/orm/queryguide.rst +++ b/doc/build/orm/queryguide.rst @@ -309,6 +309,7 @@ The :class:`_orm.aliased` construct is also central to making use of subqueries with the ORM; the sections :ref:`orm_queryguide_subqueries` and :ref:`orm_queryguide_join_subqueries` discusses this further. + .. _orm_queryguide_selecting_text: Getting ORM Results from Textual and Core Statements @@ -491,7 +492,6 @@ and order by criteria based on its exported columns:: :ref:`tutorial_orm_union` - in the :ref:`unified_tutorial` - .. _orm_queryguide_joins: Joins diff --git a/doc/build/orm/relationships.rst b/doc/build/orm/relationships.rst index 8a4fe36a1d..b9111741cc 100644 --- a/doc/build/orm/relationships.rst +++ b/doc/build/orm/relationships.rst @@ -7,7 +7,7 @@ Relationship Configuration This section describes the :func:`relationship` function and in depth discussion of its usage. For an introduction to relationships, start with the -:ref:`ormtutorial_toplevel` and head into :ref:`orm_tutorial_relationship`. +:ref:`ormtutorial_toplevel` and head into :ref:`tutorial_orm_related_objects`. .. toctree:: :maxdepth: 3 diff --git a/doc/build/orm/self_referential.rst b/doc/build/orm/self_referential.rst index b1afb1a461..ac17e6f4d2 100644 --- a/doc/build/orm/self_referential.rst +++ b/doc/build/orm/self_referential.rst @@ -137,7 +137,7 @@ the foreign key from one level of the tree to the next. In SQL, a join from a table to itself requires that at least one side of the expression be "aliased" so that it can be unambiguously referred to. -Recall from :ref:`ormtutorial_aliases` in the ORM tutorial that the +Recall from :ref:`orm_queryguide_orm_aliases` in the ORM tutorial that the :func:`_orm.aliased` construct is normally used to provide an "alias" of an ORM entity. Joining from ``Node`` to itself using this technique looks like: diff --git a/doc/build/orm/session_basics.rst b/doc/build/orm/session_basics.rst index 22c391d540..bad011d8e3 100644 --- a/doc/build/orm/session_basics.rst +++ b/doc/build/orm/session_basics.rst @@ -711,7 +711,7 @@ values for ``synchronize_session`` are supported: automatically. If the operation is against multiple tables, typically individual UPDATE / DELETE statements against the individual tables should be used. Some databases support multiple table UPDATEs. - Similar guidelines as those detailed at :ref:`multi_table_updates` + Similar guidelines as those detailed at :ref:`tutorial_update_from` may be applied. * The WHERE criteria needed in order to limit the polymorphic identity to diff --git a/doc/build/tutorial/data_insert.rst b/doc/build/tutorial/data_insert.rst index a8b1a49a25..767e7995bd 100644 --- a/doc/build/tutorial/data_insert.rst +++ b/doc/build/tutorial/data_insert.rst @@ -8,6 +8,7 @@ .. rst-class:: core-header + .. _tutorial_core_insert: Inserting Rows with Core diff --git a/doc/build/tutorial/data_select.rst b/doc/build/tutorial/data_select.rst index c8fac288e6..f30f7a5873 100644 --- a/doc/build/tutorial/data_select.rst +++ b/doc/build/tutorial/data_select.rst @@ -248,7 +248,7 @@ when referring to arbitrary SQL expressions in a result row by name: :ref:`tutorial_order_by_label` - the label names we create may also be referred towards in the ORDER BY or GROUP BY clause of the :class:`_sql.Select`. -.. _tutorial_select_arbtrary_text: +.. _tutorial_select_arbitrary_text: Selecting with Textual Column Expressions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1050,6 +1050,75 @@ The statement then can return the data for this column like any other: ('sandy', 'sandy@squirrelpower.org', 2)] {opensql}ROLLBACK{stop} + +.. _tutorial_lateral_correlation: + +LATERAL correlation +~~~~~~~~~~~~~~~~~~~ + +LATERAL correlation is a special sub-category of SQL correlation which +allows a selectable unit to refer to another selectable unit within a +single FROM clause. This is an extremely special use case which, while +part of the SQL standard, is only known to be supported by recent +versions of PostgreSQL. + +Normally, if a SELECT statement refers to +``table1 JOIN (SELECT ...) AS subquery`` in its FROM clause, the subquery +on the right side may not refer to the "table1" expression from the left side; +correlation may only refer to a table that is part of another SELECT that +entirely encloses this SELECT. The LATERAL keyword allows us to turn this +behavior around and allow correlation from the right side JOIN. + +SQLAlchemy supports this feature using the :meth:`_expression.Select.lateral` +method, which creates an object known as :class:`.Lateral`. :class:`.Lateral` +is in the same family as :class:`.Subquery` and :class:`.Alias`, but also +includes correlation behavior when the construct is added to the FROM clause of +an enclosing SELECT. The following example illustrates a SQL query that makes +use of LATERAL, selecting the "user account / count of email address" data as +was discussed in the previous section:: + + >>> subq = ( + ... select( + ... func.count(address_table.c.id).label("address_count"), + ... address_table.c.email_address, + ... address_table.c.user_id, + ... ). + ... where(user_table.c.id == address_table.c.user_id). + ... lateral() + ... ) + >>> stmt = select( + ... user_table.c.name, + ... subq.c.address_count, + ... subq.c.email_address + ... ).\ + ... join_from(user_table, subq).\ + ... order_by(user_table.c.id, subq.c.email_address) + >>> print(stmt) + {opensql}SELECT user_account.name, anon_1.address_count, anon_1.email_address + FROM user_account + JOIN LATERAL (SELECT count(address.id) AS address_count, + address.email_address AS email_address, address.user_id AS user_id + FROM address + WHERE user_account.id = address.user_id) AS anon_1 + ON user_account.id = anon_1.user_id + ORDER BY user_account.id, anon_1.email_address + +Above, the right side of the JOIN is a subquery that correlates to the +``user_account`` table that's on the left side of the join. + +When using :meth:`_expression.Select.lateral`, the behavior of +:meth:`_expression.Select.correlate` and +:meth:`_expression.Select.correlate_except` methods is applied to the +:class:`.Lateral` construct as well. + +.. seealso:: + + :class:`_expression.Lateral` + + :meth:`_expression.Select.lateral` + + + .. _tutorial_union: UNION, UNION ALL and other set operations @@ -1258,6 +1327,7 @@ clause: [('patrick',)] {opensql}ROLLBACK{stop} + .. _tutorial_functions: Working with SQL Functions @@ -1577,8 +1647,8 @@ using the :meth:`_functions.FunctionElement.filter` method:: count(address.email_address) FILTER (WHERE user_account.name = ?) AS anon_2 FROM user_account JOIN address ON user_account.id = address.user_id [...] ('sandy', 'spongebob') - [(2, 1)] - ROLLBACK + {stop}[(2, 1)] + {opensql}ROLLBACK .. _tutorial_functions_table_valued: @@ -1614,16 +1684,16 @@ modern versions of SQLite:: >>> onetwothree = func.json_each('["one", "two", "three"]').table_valued("value") >>> stmt = select(onetwothree).where(onetwothree.c.value.in_(["two", "three"])) - >>> with engine.connect() as conn: # doctest:+SKIP + >>> with engine.connect() as conn: ... result = conn.execute(stmt) - ... print(result.all()) + ... result.all() {opensql}BEGIN (implicit) SELECT anon_1.value FROM json_each(?) AS anon_1 WHERE anon_1.value IN (?, ?) [...] ('["one", "two", "three"]', 'two', 'three') - [('two',), ('three',)] - ROLLBACK + {stop}[('two',), ('three',)] + {opensql}ROLLBACK{stop} Above, we used the ``json_each()`` JSON function supported by SQLite and PostgreSQL to generate a table valued expression with a single column referred @@ -1671,4 +1741,77 @@ it is usable for custom SQL functions:: :ref:`postgresql_column_valued` - in the :ref:`postgresql_toplevel` documentation. +.. _tutorial_casts: + +Data Casts and Type Coercion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In SQL, we often need to indicate the datatype of an expression explicitly, +either to tell the database what type is expected in an otherwise ambiguous +expression, or in some cases when we want to convert the implied datatype +of a SQL expression into something else. The SQL CAST keyword is used for +this task, which in SQLAlchemy is provided by the :func:`.cast` function. +This function accepts a column expression and a data type +object as arguments, as demonstrated below where we produce a SQL expression +``CAST(user_account.id AS VARCHAR)`` from the ``user_table.c.id`` column +object:: + + >>> from sqlalchemy import cast + >>> stmt = select(cast(user_table.c.id, String)) + >>> with engine.connect() as conn: + ... result = conn.execute(stmt) + ... result.all() + {opensql}BEGIN (implicit) + SELECT CAST(user_account.id AS VARCHAR) AS id + FROM user_account + [...] () + {stop}[('1',), ('2',), ('3',)] + {opensql}ROLLBACK{stop} + +The :func:`.cast` function not only renders the SQL CAST syntax, it also +produces a SQLAlchemy column expression that will act as the given datatype on +the Python side as well. A string expression that is :func:`.cast` to +:class:`_sqltypes.JSON` will gain JSON subscript and comparison operators, for example:: + >>> from sqlalchemy import JSON + >>> print(cast("{'a': 'b'}", JSON)["a"]) + CAST(:param_1 AS JSON)[:param_2] + + +type_coerce() - a Python-only "cast" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes there is the need to have SQLAlchemy know the datatype of an +expression, for all the reasons mentioned above, but to not render the CAST +expression itself on the SQL side, where it may interfere with a SQL operation +that already works without it. For this fairly common use case there is +another function :func:`.type_coerce` which is closely related to +:func:`.cast`, in that it sets up a Python expression as having a specific SQL +database type, but does not render the ``CAST`` keyword or datatype on the +database side. :func:`.type_coerce` is particularly important when dealing +with the :class:`_types.JSON` datatype, which typically has an intricate +relationship with string-oriented datatypes on different platforms and +may not even be an explicit datatype, such as on SQLite and MariaDB. +Below, we use :func:`.type_coerce` to deliver a Python structure as a JSON +string into one of MySQL's JSON functions: + +.. sourcecode:: pycon+sql + + >>> import json + >>> from sqlalchemy import JSON + >>> from sqlalchemy import type_coerce + >>> from sqlalchemy.dialects import mysql + >>> s = select( + ... type_coerce( + ... {'some_key': {'foo': 'bar'}}, JSON + ... )['some_key'] + ... ) + >>> print(s.compile(dialect=mysql.dialect())) + SELECT JSON_EXTRACT(%s, %s) AS anon_1 + +Above, MySQL's ``JSON_EXTRACT`` SQL function was invoked +because we used :func:`.type_coerce` to indicate that our Python dictionary +should be treated as :class:`_types.JSON`. The Python ``__getitem__`` +operator, ``['some_key']`` in this case, became available as a result and +allowed a ``JSON_EXTRACT`` path expression (not shown, however in this +case it would ultimately be ``'$."some_key"'``) to be rendered. diff --git a/doc/build/tutorial/dbapi_transactions.rst b/doc/build/tutorial/dbapi_transactions.rst index e5a499786e..545a0d1291 100644 --- a/doc/build/tutorial/dbapi_transactions.rst +++ b/doc/build/tutorial/dbapi_transactions.rst @@ -179,6 +179,7 @@ purposes. .. rst-class:: core-header +.. _tutorial_statement_execution: Basics of Statement Execution ----------------------------- diff --git a/doc/build/tutorial/orm_related_objects.rst b/doc/build/tutorial/orm_related_objects.rst index 59691cf818..2eacc39e36 100644 --- a/doc/build/tutorial/orm_related_objects.rst +++ b/doc/build/tutorial/orm_related_objects.rst @@ -5,6 +5,7 @@ .. include:: tutorial_nav_include.rst + .. _tutorial_orm_related_objects: Working with Related Objects @@ -129,6 +130,9 @@ of the ``Address.user`` attribute after the fact:: # equivalent effect as a2 = Address(user=u1) >>> a2.user = u1 + +.. _tutorial_orm_cascades: + Cascading Objects into the Session ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index b585ea992c..f91f3ef337 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -562,7 +562,7 @@ forms are accepted, including a single dictionary: as well as a list of 2-tuples, which will automatically provide a parameter-ordered UPDATE statement in a manner similar to that described -at :ref:`updates_order_parameters`. Unlike the :class:`_expression.Update` +at :ref:`tutorial_parameter_ordered_updates`. Unlike the :class:`_expression.Update` object, no special flag is needed to specify the intent since the argument form is this context is unambiguous: diff --git a/lib/sqlalchemy/dialects/mysql/dml.py b/lib/sqlalchemy/dialects/mysql/dml.py index f5e4f03e97..b93116c389 100644 --- a/lib/sqlalchemy/dialects/mysql/dml.py +++ b/lib/sqlalchemy/dialects/mysql/dml.py @@ -128,7 +128,7 @@ class Insert(StandardInsert): in the UPDATE clause should be ordered as sent, in a manner similar to that described for the :class:`_expression.Update` construct overall - in :ref:`updates_order_parameters`:: + in :ref:`tutorial_parameter_ordered_updates`:: insert().on_duplicate_key_update( [("name", "some name"), ("value", "some value")]) diff --git a/lib/sqlalchemy/dialects/postgresql/psycopg2.py b/lib/sqlalchemy/dialects/postgresql/psycopg2.py index f5d84a5a35..cf0ac87f01 100644 --- a/lib/sqlalchemy/dialects/postgresql/psycopg2.py +++ b/lib/sqlalchemy/dialects/postgresql/psycopg2.py @@ -205,7 +205,7 @@ performance, primarily with INSERT statements, by multiple orders of magnitude. SQLAlchemy internally makes use of these extensions for ``executemany()`` style calls, which correspond to lists of parameters being passed to :meth:`_engine.Connection.execute` as detailed in :ref:`multiple parameter -sets `. The ORM also uses this mode internally whenever +sets `. The ORM also uses this mode internally whenever possible. The two available extensions on the psycopg2 side are the ``execute_values()`` @@ -281,7 +281,7 @@ size defaults to 100. These can be affected by passing new values to .. seealso:: - :ref:`execute_multiple` - General information on using the + :ref:`tutorial_multiple_parameters` - General information on using the :class:`_engine.Connection` object to execute statements in such a way as to make use of the DBAPI ``.executemany()`` method. diff --git a/lib/sqlalchemy/engine/cursor.py b/lib/sqlalchemy/engine/cursor.py index ec1e1abe18..34a762a15f 100644 --- a/lib/sqlalchemy/engine/cursor.py +++ b/lib/sqlalchemy/engine/cursor.py @@ -1244,7 +1244,7 @@ class CursorResult(Result[_T]): .. seealso:: - :ref:`coretutorial_selecting` - introductory material for accessing + :ref:`tutorial_selecting_data` - introductory material for accessing :class:`_engine.CursorResult` and :class:`.Row` objects. """ diff --git a/lib/sqlalchemy/engine/row.py b/lib/sqlalchemy/engine/row.py index 7c9eacb78c..06976dd4ba 100644 --- a/lib/sqlalchemy/engine/row.py +++ b/lib/sqlalchemy/engine/row.py @@ -65,7 +65,7 @@ class Row(BaseRow, Sequence[Any], Generic[_TP]): .. seealso:: - :ref:`coretutorial_selecting` - includes examples of selecting + :ref:`tutorial_selecting_data` - includes examples of selecting rows from SELECT statements. .. versionchanged:: 1.4 diff --git a/lib/sqlalchemy/engine/url.py b/lib/sqlalchemy/engine/url.py index 5558b397c5..6dea3677e9 100644 --- a/lib/sqlalchemy/engine/url.py +++ b/lib/sqlalchemy/engine/url.py @@ -75,13 +75,7 @@ class URL(NamedTuple): * :attr:`_engine.URL.drivername`: database backend and driver name, such as ``postgresql+psycopg2`` * :attr:`_engine.URL.username`: username string - * :attr:`_engine.URL.password`: password string, or object that includes - a ``__str__()`` method that produces a password. - - .. note:: A password-producing object will be stringified only - **once** per :class:`_engine.Engine` object. For dynamic password - generation per connect, see :ref:`engines_dynamic_tokens`. - + * :attr:`_engine.URL.password`: password string * :attr:`_engine.URL.host`: string hostname * :attr:`_engine.URL.port`: integer port number * :attr:`_engine.URL.database`: string database name @@ -93,12 +87,57 @@ class URL(NamedTuple): """ drivername: str + """database backend and driver name, such as + ``postgresql+psycopg2`` + + """ + username: Optional[str] + "username string" + password: Optional[str] + """password, which is normally a string but may also be any + object that has a ``__str__()`` method.""" + host: Optional[str] + """hostname or IP number. May also be a data source name for some + drivers.""" + port: Optional[int] + """integer port number""" + database: Optional[str] + """database name""" + query: util.immutabledict[str, Union[Tuple[str, ...], str]] + """an immutable mapping representing the query string. contains strings + for keys and either strings or tuples of strings for values, e.g.:: + + >>> from sqlalchemy.engine import make_url + >>> url = make_url("postgresql+psycopg2://user:pass@host/dbname?alt_host=host1&alt_host=host2&ssl_cipher=%2Fpath%2Fto%2Fcrt") + >>> url.query + immutabledict({'alt_host': ('host1', 'host2'), 'ssl_cipher': '/path/to/crt'}) + + To create a mutable copy of this mapping, use the ``dict`` constructor:: + + mutable_query_opts = dict(url.query) + + .. seealso:: + + :attr:`_engine.URL.normalized_query` - normalizes all values into sequences + for consistent processing + + Methods for altering the contents of :attr:`_engine.URL.query`: + + :meth:`_engine.URL.update_query_dict` + + :meth:`_engine.URL.update_query_string` + + :meth:`_engine.URL.update_query_pairs` + + :meth:`_engine.URL.difference_update_query` + + """ # noqa: E501 @classmethod def create( diff --git a/lib/sqlalchemy/ext/asyncio/result.py b/lib/sqlalchemy/ext/asyncio/result.py index ff3dcf4174..8a1b1be32e 100644 --- a/lib/sqlalchemy/ext/asyncio/result.py +++ b/lib/sqlalchemy/ext/asyncio/result.py @@ -18,6 +18,7 @@ from typing import TypeVar from . import exc as async_exc from ... import util +from ...engine import Result from ...engine.result import _NO_ROW from ...engine.result import _R from ...engine.result import FilterResult @@ -31,7 +32,6 @@ from ...util.typing import Literal if TYPE_CHECKING: from ...engine import CursorResult - from ...engine import Result from ...engine.result import _KeyIndexType from ...engine.result import _UniqueFilterType from ...engine.result import RMKeyView diff --git a/lib/sqlalchemy/orm/_orm_constructors.py b/lib/sqlalchemy/orm/_orm_constructors.py index ece6a52be8..e682828650 100644 --- a/lib/sqlalchemy/orm/_orm_constructors.py +++ b/lib/sqlalchemy/orm/_orm_constructors.py @@ -856,7 +856,7 @@ def relationship( :ref:`relationship_config_toplevel` - Full introductory and reference documentation for :func:`_orm.relationship`. - :ref:`orm_tutorial_relationship` - ORM tutorial introduction. + :ref:`tutorial_orm_related_objects` - ORM tutorial introduction. :param argument: A mapped class, or actual :class:`_orm.Mapper` instance, @@ -923,9 +923,6 @@ def relationship( :ref:`relationships_many_to_many` - Reference example of "many to many". - :ref:`orm_tutorial_many_to_many` - ORM tutorial introduction to - many-to-many relationships. - :ref:`self_referential_many_to_many` - Specifics on using many-to-many in a self-referential case. @@ -1029,9 +1026,6 @@ def relationship( :ref:`unitofwork_cascades` - Full detail on each of the available cascade options. - :ref:`tutorial_delete_cascade` - Tutorial example describing - a delete cascade. - :param cascade_backrefs=False: Legacy; this flag is always False. @@ -2095,8 +2089,6 @@ def aliased( :ref:`orm_queryguide_orm_aliases` - in the :ref:`queryguide_toplevel` - :ref:`ormtutorial_aliases` - in the legacy :ref:`ormtutorial_toplevel` - :param element: element to be aliased. Is normally a mapped class, but for convenience can also be a :class:`_expression.FromClause` element. diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 419891708c..596e910998 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -3050,9 +3050,8 @@ class Query( :param values: a dictionary with attributes names, or alternatively mapped attributes or SQL expressions, as keys, and literal values or sql expressions as values. If :ref:`parameter-ordered - mode ` is desired, the values can be - passed as a list of 2-tuples; - this requires that the + mode ` is desired, the values can + be passed as a list of 2-tuples; this requires that the :paramref:`~sqlalchemy.sql.expression.update.preserve_parameter_order` flag is passed to the :paramref:`.Query.update.update_args` dictionary as well. diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py index deaf521472..630f6898fa 100644 --- a/lib/sqlalchemy/orm/relationships.py +++ b/lib/sqlalchemy/orm/relationships.py @@ -985,7 +985,7 @@ class Relationship( See :meth:`~.Relationship.Comparator.any` for a less-performant alternative using EXISTS, or refer to :meth:`_query.Query.outerjoin` - as well as :ref:`ormtutorial_joins` + as well as :ref:`orm_queryguide_joins` for more details on constructing outer joins. kwargs may be ignored by this operator but are required for API diff --git a/lib/sqlalchemy/sql/_dml_constructors.py b/lib/sqlalchemy/sql/_dml_constructors.py index 926e5257ba..293d225f98 100644 --- a/lib/sqlalchemy/sql/_dml_constructors.py +++ b/lib/sqlalchemy/sql/_dml_constructors.py @@ -35,9 +35,6 @@ def insert(table: _DMLTableArgument) -> Insert: .. seealso:: - :ref:`coretutorial_insert_expressions` - in the - :ref:`1.x tutorial ` - :ref:`tutorial_core_insert` - in the :ref:`unified_tutorial` @@ -79,9 +76,7 @@ def insert(table: _DMLTableArgument) -> Insert: .. seealso:: - :ref:`coretutorial_insert_expressions` - SQL Expression Tutorial - - :ref:`inserts_and_updates` - SQL Expression Tutorial + :ref:`tutorial_core_insert` - in the :ref:`unified_tutorial` """ return Insert(table) @@ -104,15 +99,6 @@ def update(table: _DMLTableArgument) -> Update: :meth:`_expression.TableClause.update` method on :class:`_schema.Table`. - .. seealso:: - - :ref:`inserts_and_updates` - in the - :ref:`1.x tutorial ` - - :ref:`tutorial_core_update_delete` - in the :ref:`unified_tutorial` - - - :param table: A :class:`_schema.Table` object representing the database table to be updated. diff --git a/lib/sqlalchemy/sql/_elements_constructors.py b/lib/sqlalchemy/sql/_elements_constructors.py index f6dd928654..8b8f6b010e 100644 --- a/lib/sqlalchemy/sql/_elements_constructors.py +++ b/lib/sqlalchemy/sql/_elements_constructors.py @@ -605,15 +605,6 @@ def bindparam( .. versionchanged:: 1.3 the "expanding" bound parameter feature now supports empty lists. - - .. seealso:: - - :ref:`coretutorial_bind_param` - - :ref:`coretutorial_insert_expressions` - - :func:`.outparam` - :param literal_execute: if True, the bound parameter will be rendered in the compile phase with a special "POSTCOMPILE" token, and the SQLAlchemy compiler will @@ -635,6 +626,12 @@ def bindparam( :ref:`change_4808`. + .. seealso:: + + :ref:`tutorial_sending_parameters` - in the + :ref:`unified_tutorial` + + """ return BindParameter( key, @@ -827,7 +824,7 @@ def cast( .. seealso:: - :ref:`coretutorial_casts` + :ref:`tutorial_casts` :func:`.type_coerce` - an alternative to CAST that coerces the type on the Python side only, which is often sufficient to generate the @@ -932,7 +929,7 @@ def column( :func:`_expression.text` - :ref:`sqlexpression_literal_column` + :ref:`tutorial_select_arbitrary_text` """ return ColumnClause(text, type_, is_literal, _selectable) @@ -1468,8 +1465,7 @@ def text(text: str) -> TextClause: .. seealso:: - :ref:`sqlexpression_text` - in the Core tutorial - + :ref:`tutorial_select_arbitrary_text` """ return TextClause(text) @@ -1615,7 +1611,7 @@ def type_coerce( .. seealso:: - :ref:`coretutorial_casts` + :ref:`tutorial_casts` :func:`.cast` diff --git a/lib/sqlalchemy/sql/_selectable_constructors.py b/lib/sqlalchemy/sql/_selectable_constructors.py index ea824d6229..b661d6f477 100644 --- a/lib/sqlalchemy/sql/_selectable_constructors.py +++ b/lib/sqlalchemy/sql/_selectable_constructors.py @@ -290,7 +290,7 @@ def lateral( .. seealso:: - :ref:`lateral_selects` - overview of usage. + :ref:`tutorial_lateral_correlation` - overview of usage. """ return Lateral._factory(selectable, name=name) @@ -466,8 +466,7 @@ def select(*entities: _ColumnsClauseArgument[Any], **__kw: Any) -> Select[Any]: .. seealso:: - :ref:`coretutorial_selecting` - Core Tutorial description of - :func:`_expression.select`. + :ref:`tutorial_selecting_data` - in the :ref:`unified_tutorial` :param \*entities: Entities to SELECT from. For Core usage, this is typically a series diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py index e63a34454d..955eb4109c 100644 --- a/lib/sqlalchemy/sql/dml.py +++ b/lib/sqlalchemy/sql/dml.py @@ -780,7 +780,7 @@ class ValuesBase(UpdateBase): .. seealso:: - :ref:`execute_multiple` - an introduction to + :ref:`tutorial_multiple_parameters` - an introduction to the traditional Core method of multiple parameter set invocation for INSERTs and other statements. @@ -1236,16 +1236,6 @@ class DMLWhereBase: .. seealso:: - **1.x Tutorial Examples** - - :ref:`tutorial_1x_correlated_updates` - - :ref:`multi_table_updates` - - :ref:`multi_table_deletes` - - **2.0 Tutorial Examples** - :ref:`tutorial_correlated_updates` :ref:`tutorial_update_from` @@ -1361,7 +1351,7 @@ class Update(DMLWhereBase, ValuesBase): .. seealso:: - :ref:`updates_order_parameters` - full example of the + :ref:`tutorial_parameter_ordered_updates` - full example of the :meth:`_expression.Update.ordered_values` method. .. versionchanged:: 1.4 The :meth:`_expression.Update.ordered_values` diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index 6032253c26..625e1d94bd 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -190,7 +190,7 @@ def literal_column( :func:`_expression.text` - :ref:`sqlexpression_literal_column` + :ref:`tutorial_select_arbitrary_text` """ return ColumnClause(text, type_=type_, is_literal=True) @@ -1568,7 +1568,7 @@ class ColumnElement( .. seealso:: - :ref:`coretutorial_casts` + :ref:`tutorial_casts` :func:`_expression.cast` @@ -3198,7 +3198,7 @@ class Cast(WrapsColumnExpression[_T]): .. seealso:: - :ref:`coretutorial_casts` + :ref:`tutorial_casts` :func:`.cast` diff --git a/lib/sqlalchemy/sql/functions.py b/lib/sqlalchemy/sql/functions.py index 0cba1a1a8f..befd262ecf 100644 --- a/lib/sqlalchemy/sql/functions.py +++ b/lib/sqlalchemy/sql/functions.py @@ -102,7 +102,7 @@ class FunctionElement(Executable, ColumnElement[_T], FromClause, Generative): .. seealso:: - :ref:`coretutorial_functions` - in the Core tutorial + :ref:`tutorial_functions` - in the :ref:`unified_tutorial` :class:`.Function` - named SQL function. @@ -821,7 +821,7 @@ class _FunctionGenerator: .. seealso:: - :ref:`coretutorial_functions` - in the Core Tutorial + :ref:`tutorial_functions` - in the :ref:`unified_tutorial` :class:`.Function` diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 53dcf51c77..fe19e2d7f6 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -105,6 +105,7 @@ and_ = BooleanClauseList.and_ _T = TypeVar("_T", bound=Any) if TYPE_CHECKING: + import sqlalchemy from ._typing import _ColumnExpressionArgument from ._typing import _FromClauseArgument from ._typing import _JoinTargetArgument @@ -129,7 +130,6 @@ if TYPE_CHECKING: from .cache_key import _CacheKeyTraversalType from .compiler import SQLCompiler from .dml import Delete - from .dml import Insert from .dml import Update from .elements import KeyedColumnElement from .elements import Label @@ -292,7 +292,7 @@ class Selectable(ReturnsRows): .. seealso:: - :ref:`lateral_selects` - overview of usage. + :ref:`tutorial_lateral_correlation` - overview of usage. """ return Lateral._construct(self, name) @@ -751,7 +751,7 @@ class FromClause(roles.AnonymizedFromClauseRole, Selectable): .. seealso:: - :ref:`core_tutorial_aliases` + :ref:`tutorial_using_aliases` :func:`_expression.alias` @@ -1889,7 +1889,7 @@ class Lateral(FromClauseAlias, LateralFromClause): .. seealso:: - :ref:`lateral_selects` - overview of usage. + :ref:`tutorial_lateral_correlation` - overview of usage. """ @@ -2059,7 +2059,7 @@ class CTE( .. seealso:: - :ref:`core_tutorial_aliases` + :ref:`tutorial_using_aliases` :func:`_expression.alias` @@ -2992,7 +2992,7 @@ class TableClause(roles.DMLTableRole, Immutable, NamedFromClause): c.table = self @util.preload_module("sqlalchemy.sql.dml") - def insert(self) -> Insert: + def insert(self) -> sqlalchemy.sql.expression.Insert: """Generate an :func:`_expression.insert` construct against this :class:`_expression.TableClause`. @@ -3176,7 +3176,7 @@ class Values(Generative, LateralFromClause): .. seealso:: - :ref:`core_tutorial_aliases` + :ref:`tutorial_using_aliases` :func:`_expression.alias` @@ -3458,8 +3458,6 @@ class SelectBase( :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial - :ref:`scalar_selects` - in the 1.x tutorial - """ if self._label_style is not LABEL_STYLE_NONE: self = self.set_label_style(LABEL_STYLE_NONE) @@ -3487,7 +3485,7 @@ class SelectBase( .. seealso:: - :ref:`lateral_selects` - overview of usage. + :ref:`tutorial_lateral_correlation` - overview of usage. """ return Lateral._factory(self, name) @@ -4966,8 +4964,6 @@ class Select( :func:`_sql.select` - :ref:`coretutorial_selecting` - in the 1.x tutorial - :ref:`tutorial_selecting_data` - in the 2.0 tutorial """ @@ -6018,7 +6014,7 @@ class Select( :meth:`_expression.Select.correlate_except` - :ref:`correlated_subqueries` + :ref:`tutorial_scalar_subquery` """ @@ -6068,7 +6064,7 @@ class Select( :meth:`_expression.Select.correlate` - :ref:`correlated_subqueries` + :ref:`tutorial_scalar_subquery` """ @@ -6341,8 +6337,6 @@ class ScalarSelect( :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial - :ref:`scalar_selects` - in the 1.x tutorial - """ _traverse_internals: _TraverseInternalsType = [ @@ -6440,8 +6434,6 @@ class ScalarSelect( :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial - :ref:`correlated_subqueries` - in the 1.x tutorial - """ self.element = cast("Select[Any]", self.element).correlate( @@ -6479,8 +6471,6 @@ class ScalarSelect( :ref:`tutorial_scalar_subquery` - in the 2.0 tutorial - :ref:`correlated_subqueries` - in the 1.x tutorial - """