diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2022-03-26 16:20:34 -0400 |
|---|---|---|
| committer | mike bayer <mike_mp@zzzcomputing.com> | 2022-03-28 20:50:33 +0000 |
| commit | 9a8d95ce05a88c1efb02b52a88ff350d5d751d91 (patch) | |
| tree | 80497111ccd0ec2fe55328943b25c24e3922e92a /lib | |
| parent | c90396fbe7424c481f8f4ee18b6cedd1fa09c711 (diff) | |
| download | sqlalchemy-9a8d95ce05a88c1efb02b52a88ff350d5d751d91.tar.gz | |
column_descriptions or equiv for DML, core select
Added new attributes :attr:`.ValuesBase.returning_column_descriptions` and
:attr:`.ValuesBase.entity_description` to allow for inspection of ORM
attributes and entities that are installed as part of an :class:`.Insert`,
:class:`.Update`, or :class:`.Delete` construct. The
:attr:`.Select.column_descriptions` accessor is also now implemented for
Core-only selectables.
Fixes: #7861
Change-Id: Ia6a1cd24c798ba61f4e8e8eac90a0fd00d738342
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/orm/persistence.py | 56 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/query.py | 9 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/dml.py | 98 | ||||
| -rw-r--r-- | lib/sqlalchemy/sql/selectable.py | 50 |
4 files changed, 208 insertions, 5 deletions
diff --git a/lib/sqlalchemy/orm/persistence.py b/lib/sqlalchemy/orm/persistence.py index 519eb393f..6478aac15 100644 --- a/lib/sqlalchemy/orm/persistence.py +++ b/lib/sqlalchemy/orm/persistence.py @@ -42,6 +42,7 @@ from ..sql.base import _entity_namespace_key from ..sql.base import CompileState from ..sql.base import Options from ..sql.dml import DeleteDMLState +from ..sql.dml import InsertDMLState from ..sql.dml import UpdateDMLState from ..sql.elements import BooleanClauseList from ..sql.selectable import LABEL_STYLE_TABLENAME_PLUS_COL @@ -2133,8 +2134,59 @@ class BulkUDCompileState(CompileState): } +class ORMDMLState: + @classmethod + def get_entity_description(cls, statement): + ext_info = statement.table._annotations["parententity"] + mapper = ext_info.mapper + if ext_info.is_aliased_class: + _label_name = ext_info.name + else: + _label_name = mapper.class_.__name__ + + return { + "name": _label_name, + "type": mapper.class_, + "expr": ext_info.entity, + "entity": ext_info.entity, + "table": mapper.local_table, + } + + @classmethod + def get_returning_column_descriptions(cls, statement): + def _ent_for_col(c): + return c._annotations.get("parententity", None) + + def _attr_for_col(c, ent): + if ent is None: + return c + proxy_key = c._annotations.get("proxy_key", None) + if not proxy_key: + return c + else: + return getattr(ent.entity, proxy_key, c) + + return [ + { + "name": c.key, + "type": c.type, + "expr": _attr_for_col(c, ent), + "aliased": ent.is_aliased_class, + "entity": ent.entity, + } + for c, ent in [ + (c, _ent_for_col(c)) for c in statement._all_selected_columns + ] + ] + + +@CompileState.plugin_for("orm", "insert") +class ORMInsert(ORMDMLState, InsertDMLState): + pass + + @CompileState.plugin_for("orm", "update") -class BulkORMUpdate(UpdateDMLState, BulkUDCompileState): +class BulkORMUpdate(ORMDMLState, UpdateDMLState, BulkUDCompileState): @classmethod def create_for_statement(cls, statement, compiler, **kw): @@ -2352,7 +2404,7 @@ class BulkORMUpdate(UpdateDMLState, BulkUDCompileState): @CompileState.plugin_for("orm", "delete") -class BulkORMDelete(DeleteDMLState, BulkUDCompileState): +class BulkORMDelete(ORMDMLState, DeleteDMLState, BulkUDCompileState): @classmethod def create_for_statement(cls, statement, compiler, **kw): self = cls.__new__(cls) diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 23dbfc32c..ea5d5406e 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -2391,6 +2391,15 @@ class Query( } ] + .. seealso:: + + This API is available using :term:`2.0 style` queries as well, + documented at: + + * :ref:`queryguide_inspection` + + * :attr:`.Select.column_descriptions` + """ return _column_descriptions(self, legacy=True) diff --git a/lib/sqlalchemy/sql/dml.py b/lib/sqlalchemy/sql/dml.py index 10316dd2b..f5fb6b2f3 100644 --- a/lib/sqlalchemy/sql/dml.py +++ b/lib/sqlalchemy/sql/dml.py @@ -78,6 +78,21 @@ class DMLState(CompileState): def __init__(self, statement, compiler, **kw): raise NotImplementedError() + @classmethod + def get_entity_description(cls, statement): + return {"name": statement.table.name, "table": statement.table} + + @classmethod + def get_returning_column_descriptions(cls, statement): + return [ + { + "name": c.key, + "type": c.type, + "expr": c, + } + for c in statement._all_selected_columns + ] + @property def dml_table(self): return self.statement.table @@ -438,6 +453,89 @@ class UpdateBase( self._hints = self._hints.union({(selectable, dialect_name): text}) return self + @property + def entity_description(self): + """Return a :term:`plugin-enabled` description of the table and/or entity + which this DML construct is operating against. + + This attribute is generally useful when using the ORM, as an + extended structure which includes information about mapped + entities is returned. The section :ref:`queryguide_inspection` + contains more background. + + For a Core statement, the structure returned by this accessor + is derived from the :attr:`.UpdateBase.table` attribute, and + refers to the :class:`.Table` being inserted, updated, or deleted:: + + >>> stmt = insert(user_table) + >>> stmt.entity_description + { + "name": "user_table", + "table": Table("user_table", ...) + } + + .. versionadded:: 1.4.33 + + .. seealso:: + + :attr:`.UpdateBase.returning_column_descriptions` + + :attr:`.Select.column_descriptions` - entity information for + a :func:`.select` construct + + :ref:`queryguide_inspection` - ORM background + + """ + meth = DMLState.get_plugin_class(self).get_entity_description + return meth(self) + + @property + def returning_column_descriptions(self): + """Return a :term:`plugin-enabled` description of the columns + which this DML construct is RETURNING against, in other words + the expressions established as part of :meth:`.UpdateBase.returning`. + + This attribute is generally useful when using the ORM, as an + extended structure which includes information about mapped + entities is returned. The section :ref:`queryguide_inspection` + contains more background. + + For a Core statement, the structure returned by this accessor is + derived from the same objects that are returned by the + :attr:`.UpdateBase.exported_columns` accessor:: + + >>> stmt = insert(user_table).returning(user_table.c.id, user_table.c.name) + >>> stmt.entity_description + [ + { + "name": "id", + "type": Integer, + "expr": Column("id", Integer(), table=<user>, ...) + }, + { + "name": "name", + "type": String(), + "expr": Column("name", String(), table=<user>, ...) + }, + ] + + .. versionadded:: 1.4.33 + + .. seealso:: + + :attr:`.UpdateBase.entity_description` + + :attr:`.Select.column_descriptions` - entity information for + a :func:`.select` construct + + :ref:`queryguide_inspection` - ORM background + + """ # noqa E501 + meth = DMLState.get_plugin_class( + self + ).get_returning_column_descriptions + return meth(self) + SelfValuesBase = typing.TypeVar("SelfValuesBase", bound="ValuesBase") diff --git a/lib/sqlalchemy/sql/selectable.py b/lib/sqlalchemy/sql/selectable.py index 260daa9e9..2f37317f2 100644 --- a/lib/sqlalchemy/sql/selectable.py +++ b/lib/sqlalchemy/sql/selectable.py @@ -3729,7 +3729,16 @@ class SelectState(util.MemoizedSlots, CompileState): @classmethod def get_column_descriptions(cls, statement): - cls._plugin_not_implemented() + return [ + { + "name": name, + "type": element.type, + "expr": element, + } + for _, name, _, element, _ in ( + statement._generate_columns_plus_names(False) + ) + ] @classmethod def from_statement(cls, statement, from_statement): @@ -4311,8 +4320,43 @@ class Select( @property def column_descriptions(self): - """Return a 'column descriptions' structure which may be - :term:`plugin-specific`. + """Return a :term:`plugin-enabled` 'column descriptions' structure + referring to the columns which are SELECTed by this statement. + + This attribute is generally useful when using the ORM, as an + extended structure which includes information about mapped + entities is returned. The section :ref:`queryguide_inspection` + contains more background. + + For a Core-only statement, the structure returned by this accessor + is derived from the same objects that are returned by the + :attr:`.Select.selected_columns` accessor, formatted as a list of + dictionaries which contain the keys ``name``, ``type`` and ``expr``, + which indicate the column expressions to be selected:: + + >>> stmt = select(user_table) + >>> stmt.column_descriptions + [ + { + 'name': 'id', + 'type': Integer(), + 'expr': Column('id', Integer(), ...)}, + { + 'name': 'name', + 'type': String(length=30), + 'expr': Column('name', String(length=30), ...)} + ] + + .. versionchanged:: 1.4.33 The :attr:`.Select.column_descriptions` + attribute returns a structure for a Core-only set of entities, + not just ORM-only entities. + + .. seealso:: + + :attr:`.UpdateBase.entity_description` - entity information for + an :func:`.insert`, :func:`.update`, or :func:`.delete` + + :ref:`queryguide_inspection` - ORM background """ meth = SelectState.get_plugin_class(self).get_column_descriptions |
