From 64952ac28b1911410f2b7c08a16492716689126d Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 8 Feb 2020 14:53:21 -0500 Subject: [PATCH] Fixes for public_factory and mysql/pg dml functions * ensure that the location indicated by public_factory is importable * adjust all of sqlalchemy.sql.expression locations to be correct * support the case where a public_factory is against a function that has another public_factory already, and already replaced the __init__ on the target class * Use mysql.insert(), postgresql.insert(), don't include .dml in the class path. Change-Id: Iac285289455d8d7102349df3814f7cedc758e639 (cherry picked from commit 660ff51df0433607b12c58a12c7355107c1773f5) --- doc/build/dialects/mysql.rst | 4 +- doc/build/dialects/postgresql.rst | 4 +- lib/sqlalchemy/dialects/mssql/base.py | 2 +- lib/sqlalchemy/dialects/mysql/base.py | 10 +-- lib/sqlalchemy/dialects/mysql/dml.py | 7 +- lib/sqlalchemy/dialects/postgresql/base.py | 12 +-- lib/sqlalchemy/dialects/postgresql/dml.py | 7 +- lib/sqlalchemy/sql/expression.py | 88 +++++++++++----------- lib/sqlalchemy/util/langhelpers.py | 34 ++++++++- 9 files changed, 104 insertions(+), 64 deletions(-) diff --git a/doc/build/dialects/mysql.rst b/doc/build/dialects/mysql.rst index 01f84d16e4..9642496be6 100644 --- a/doc/build/dialects/mysql.rst +++ b/doc/build/dialects/mysql.rst @@ -159,9 +159,9 @@ construction arguments, are as follows: MySQL DML Constructs ------------------------- -.. autofunction:: sqlalchemy.dialects.mysql.dml.insert +.. autofunction:: sqlalchemy.dialects.mysql.insert -.. autoclass:: sqlalchemy.dialects.mysql.dml.Insert +.. autoclass:: sqlalchemy.dialects.mysql.Insert :members: diff --git a/doc/build/dialects/postgresql.rst b/doc/build/dialects/postgresql.rst index a1294cc418..5fc451515a 100644 --- a/doc/build/dialects/postgresql.rst +++ b/doc/build/dialects/postgresql.rst @@ -181,9 +181,9 @@ For example:: PostgreSQL DML Constructs ------------------------- -.. autofunction:: sqlalchemy.dialects.postgresql.dml.insert +.. autofunction:: sqlalchemy.dialects.postgresql.insert -.. autoclass:: sqlalchemy.dialects.postgresql.dml.Insert +.. autoclass:: sqlalchemy.dialects.postgresql.Insert :members: psycopg2 diff --git a/lib/sqlalchemy/dialects/mssql/base.py b/lib/sqlalchemy/dialects/mssql/base.py index 10ed0cf891..839079168a 100644 --- a/lib/sqlalchemy/dialects/mssql/base.py +++ b/lib/sqlalchemy/dialects/mssql/base.py @@ -1206,7 +1206,7 @@ class TryCast(sql.elements.Cast): super(TryCast, self).__init__(*arg, **kw) -try_cast = public_factory(TryCast, ".mssql.try_cast") +try_cast = public_factory(TryCast, ".dialects.mssql.try_cast") # old names. MSDateTime = _MSDateTime diff --git a/lib/sqlalchemy/dialects/mysql/base.py b/lib/sqlalchemy/dialects/mysql/base.py index c6a4404b78..40b14ad4ab 100644 --- a/lib/sqlalchemy/dialects/mysql/base.py +++ b/lib/sqlalchemy/dialects/mysql/base.py @@ -352,8 +352,8 @@ will be performed. The statement allows for separate specification of the values to INSERT versus the values for UPDATE. SQLAlchemy provides ``ON DUPLICATE KEY UPDATE`` support via the MySQL-specific -:func:`.mysql.dml.insert()` function, which provides -the generative method :meth:`~.mysql.dml.Insert.on_duplicate_key_update`:: +:func:`.mysql.insert()` function, which provides +the generative method :meth:`~.mysql.Insert.on_duplicate_key_update`:: from sqlalchemy.dialects.mysql import insert @@ -377,7 +377,7 @@ an error or to skip performing an UPDATE. existing row, using any combination of new values as well as values from the proposed insertion. These values are normally specified using keyword arguments passed to the -:meth:`~.mysql.dml.Insert.on_duplicate_key_update` +:meth:`~.mysql.Insert.on_duplicate_key_update` given column key values (usually the name of the column, unless it specifies :paramref:`.Column.key`) as keys and literal or SQL expressions as values:: @@ -421,8 +421,8 @@ this context is unambiguous:: In order to refer to the proposed insertion row, the special alias -:attr:`~.mysql.dml.Insert.inserted` is available as an attribute on -the :class:`.mysql.dml.Insert` object; this object is a +:attr:`~.mysql.Insert.inserted` is available as an attribute on +the :class:`.mysql.Insert` object; this object is a :class:`.ColumnCollection` which contains all columns of the target table:: diff --git a/lib/sqlalchemy/dialects/mysql/dml.py b/lib/sqlalchemy/dialects/mysql/dml.py index 293aa426da..7a95f815c5 100644 --- a/lib/sqlalchemy/dialects/mysql/dml.py +++ b/lib/sqlalchemy/dialects/mysql/dml.py @@ -15,6 +15,9 @@ class Insert(StandardInsert): Adds methods for MySQL-specific syntaxes such as ON DUPLICATE KEY UPDATE. + The :class:`~.mysql.Insert` object is created using the + :func:`sqlalchemy.dialects.mysql.insert` function. + .. versionadded:: 1.2 """ @@ -106,7 +109,9 @@ class Insert(StandardInsert): return self -insert = public_factory(Insert, ".dialects.mysql.insert") +insert = public_factory( + Insert, ".dialects.mysql.insert", ".dialects.mysql.Insert" +) class OnDuplicateClause(ClauseElement): diff --git a/lib/sqlalchemy/dialects/postgresql/base.py b/lib/sqlalchemy/dialects/postgresql/base.py index 8d33219c3f..0bd2403ef7 100644 --- a/lib/sqlalchemy/dialects/postgresql/base.py +++ b/lib/sqlalchemy/dialects/postgresql/base.py @@ -304,9 +304,9 @@ or they may be *inferred* by stating the columns and conditions that comprise the indexes. SQLAlchemy provides ``ON CONFLICT`` support via the PostgreSQL-specific -:func:`.postgresql.dml.insert()` function, which provides -the generative methods :meth:`~.postgresql.dml.Insert.on_conflict_do_update` -and :meth:`~.postgresql.dml.Insert.on_conflict_do_nothing`:: +:func:`.postgresql.insert()` function, which provides +the generative methods :meth:`~.postgresql.Insert.on_conflict_do_update` +and :meth:`~.postgresql.Insert.on_conflict_do_nothing`:: from sqlalchemy.dialects.postgresql import insert @@ -415,8 +415,8 @@ for UPDATE:: :paramref:`.Insert.on_conflict_do_update.set_` dictionary. In order to refer to the proposed insertion row, the special alias -:attr:`~.postgresql.dml.Insert.excluded` is available as an attribute on -the :class:`.postgresql.dml.Insert` object; this object is a +:attr:`~.postgresql.Insert.excluded` is available as an attribute on +the :class:`.postgresql.Insert` object; this object is a :class:`.ColumnCollection` which alias contains all columns of the target table:: @@ -452,7 +452,7 @@ parameter, which will limit those rows which receive an UPDATE:: ``ON CONFLICT`` may also be used to skip inserting a row entirely if any conflict with a unique or exclusion constraint occurs; below this is illustrated using the -:meth:`~.postgresql.dml.Insert.on_conflict_do_nothing` method:: +:meth:`~.postgresql.Insert.on_conflict_do_nothing` method:: from sqlalchemy.dialects.postgresql import insert diff --git a/lib/sqlalchemy/dialects/postgresql/dml.py b/lib/sqlalchemy/dialects/postgresql/dml.py index 9251e71306..21ef45d525 100644 --- a/lib/sqlalchemy/dialects/postgresql/dml.py +++ b/lib/sqlalchemy/dialects/postgresql/dml.py @@ -23,6 +23,9 @@ class Insert(StandardInsert): Adds methods for PG-specific syntaxes such as ON CONFLICT. + The :class:`.postgresql.Insert` object is created using the + :func:`sqlalchemy.dialects.postgresql.insert` function. + .. versionadded:: 1.1 """ @@ -141,7 +144,9 @@ class Insert(StandardInsert): return self -insert = public_factory(Insert, ".dialects.postgresql.insert") +insert = public_factory( + Insert, ".dialects.postgresql.insert", ".dialects.postgresql.Insert" +) class OnConflictClause(ClauseElement): diff --git a/lib/sqlalchemy/sql/expression.py b/lib/sqlalchemy/sql/expression.py index 2c55e860a8..e02e4ee1e0 100644 --- a/lib/sqlalchemy/sql/expression.py +++ b/lib/sqlalchemy/sql/expression.py @@ -177,62 +177,66 @@ from ..util.langhelpers import public_factory # noqa # the functions to be available in the sqlalchemy.sql.* namespace and # to be auto-cross-documenting from the function to the class itself. -all_ = public_factory(CollectionAggregate._create_all, ".expression.all_") -any_ = public_factory(CollectionAggregate._create_any, ".expression.any_") -and_ = public_factory(BooleanClauseList.and_, ".expression.and_") -alias = public_factory(Alias._factory, ".expression.alias") -tablesample = public_factory(TableSample._factory, ".expression.tablesample") -lateral = public_factory(Lateral._factory, ".expression.lateral") -or_ = public_factory(BooleanClauseList.or_, ".expression.or_") -bindparam = public_factory(BindParameter, ".expression.bindparam") -select = public_factory(Select, ".expression.select") -text = public_factory(TextClause._create_text, ".expression.text") -table = public_factory(TableClause, ".expression.table") -column = public_factory(ColumnClause, ".expression.column") -over = public_factory(Over, ".expression.over") -within_group = public_factory(WithinGroup, ".expression.within_group") -label = public_factory(Label, ".expression.label") -case = public_factory(Case, ".expression.case") -cast = public_factory(Cast, ".expression.cast") -cte = public_factory(CTE._factory, ".expression.cte") -extract = public_factory(Extract, ".exp # noqaression.extract") -tuple_ = public_factory(Tuple, ".expression.tuple_") -except_ = public_factory(CompoundSelect._create_except, ".expression.except_") +all_ = public_factory(CollectionAggregate._create_all, ".sql.expression.all_") +any_ = public_factory(CollectionAggregate._create_any, ".sql.expression.any_") +and_ = public_factory(BooleanClauseList.and_, ".sql.expression.and_") +alias = public_factory(Alias._factory, ".sql.expression.alias") +tablesample = public_factory( + TableSample._factory, ".sql.expression.tablesample" +) +lateral = public_factory(Lateral._factory, ".sql.expression.lateral") +or_ = public_factory(BooleanClauseList.or_, ".sql.expression.or_") +bindparam = public_factory(BindParameter, ".sql.expression.bindparam") +select = public_factory(Select, ".sql.expression.select") +text = public_factory(TextClause._create_text, ".sql.expression.text") +table = public_factory(TableClause, ".sql.expression.table") +column = public_factory(ColumnClause, ".sql.expression.column") +over = public_factory(Over, ".sql.expression.over") +within_group = public_factory(WithinGroup, ".sql.expression.within_group") +label = public_factory(Label, ".sql.expression.label") +case = public_factory(Case, ".sql.expression.case") +cast = public_factory(Cast, ".sql.expression.cast") +cte = public_factory(CTE._factory, ".sql.expression.cte") +extract = public_factory(Extract, ".sql.expression.extract") +tuple_ = public_factory(Tuple, ".sql.expression.tuple_") +except_ = public_factory( + CompoundSelect._create_except, ".sql.expression.except_" +) except_all = public_factory( - CompoundSelect._create_except_all, ".expression.except_all" + CompoundSelect._create_except_all, ".sql.expression.except_all" ) intersect = public_factory( - CompoundSelect._create_intersect, ".expression.intersect" + CompoundSelect._create_intersect, ".sql.expression.intersect" ) intersect_all = public_factory( - CompoundSelect._create_intersect_all, ".expression.intersect_all" + CompoundSelect._create_intersect_all, ".sql.expression.intersect_all" ) -union = public_factory(CompoundSelect._create_union, ".expression.union") +union = public_factory(CompoundSelect._create_union, ".sql.expression.union") union_all = public_factory( - CompoundSelect._create_union_all, ".expression.union_all" + CompoundSelect._create_union_all, ".sql.expression.union_all" ) -exists = public_factory(Exists, ".expression.exists") +exists = public_factory(Exists, ".sql.expression.exists") nullsfirst = public_factory( - UnaryExpression._create_nullsfirst, ".expression.nullsfirst" + UnaryExpression._create_nullsfirst, ".sql.expression.nullsfirst" ) nullslast = public_factory( - UnaryExpression._create_nullslast, ".expression.nullslast" + UnaryExpression._create_nullslast, ".sql.expression.nullslast" ) -asc = public_factory(UnaryExpression._create_asc, ".expression.asc") -desc = public_factory(UnaryExpression._create_desc, ".expression.desc") +asc = public_factory(UnaryExpression._create_asc, ".sql.expression.asc") +desc = public_factory(UnaryExpression._create_desc, ".sql.expression.desc") distinct = public_factory( - UnaryExpression._create_distinct, ".expression.distinct" + UnaryExpression._create_distinct, ".sql.expression.distinct" ) -type_coerce = public_factory(TypeCoerce, ".expression.type_coerce") -true = public_factory(True_._instance, ".expression.true") -false = public_factory(False_._instance, ".expression.false") -null = public_factory(Null._instance, ".expression.null") -join = public_factory(Join._create_join, ".expression.join") -outerjoin = public_factory(Join._create_outerjoin, ".expression.outerjoin") -insert = public_factory(Insert, ".expression.insert") -update = public_factory(Update, ".expression.update") -delete = public_factory(Delete, ".expression.delete") -funcfilter = public_factory(FunctionFilter, ".expression.funcfilter") +type_coerce = public_factory(TypeCoerce, ".sql.expression.type_coerce") +true = public_factory(True_._instance, ".sql.expression.true") +false = public_factory(False_._instance, ".sql.expression.false") +null = public_factory(Null._instance, ".sql.expression.null") +join = public_factory(Join._create_join, ".sql.expression.join") +outerjoin = public_factory(Join._create_outerjoin, ".sql.expression.outerjoin") +insert = public_factory(Insert, ".sql.expression.insert") +update = public_factory(Update, ".sql.expression.update") +delete = public_factory(Delete, ".sql.expression.delete") +funcfilter = public_factory(FunctionFilter, ".sql.expression.funcfilter") # internal functions still being called from tests and the ORM, diff --git a/lib/sqlalchemy/util/langhelpers.py b/lib/sqlalchemy/util/langhelpers.py index 80ec0861fb..360c91371c 100644 --- a/lib/sqlalchemy/util/langhelpers.py +++ b/lib/sqlalchemy/util/langhelpers.py @@ -160,7 +160,7 @@ def _exec_code_in_env(code, env, fn_name): return env[fn_name] -def public_factory(target, location): +def public_factory(target, location, class_location=None): """Produce a wrapping function for the given cls or classmethod. Rationale here is so that the __init__ method of the @@ -173,14 +173,14 @@ def public_factory(target, location): doc = ( "Construct a new :class:`.%s` object. \n\n" "This constructor is mirrored as a public API function; " - "see :func:`~%s` " + "see :func:`sqlalchemy%s` " "for a full usage and argument description." % (target.__name__, location) ) else: fn = callable_ = target doc = ( - "This function is mirrored; see :func:`~%s` " + "This function is mirrored; see :func:`sqlalchemy%s` " "for a description of arguments." % location ) @@ -199,12 +199,38 @@ def %(name)s(%(args)s): env = {"cls": callable_, "symbol": symbol} exec(code, env) decorated = env[location_name] - decorated.__doc__ = fn.__doc__ + if hasattr(fn, "_linked_to"): + linked_to, linked_to_location = fn._linked_to + linked_to_doc = linked_to.__doc__ + if class_location is None: + class_location = "%s.%s" % (target.__module__, target.__name__) + + linked_to_doc = inject_docstring_text( + linked_to_doc, + ".. container:: inherited_member\n\n " + "Inherited from :func:`sqlalchemy%s`; this constructor " + "creates a :class:`%s` object" + % (linked_to_location, class_location), + 0, + ) + decorated.__doc__ = linked_to_doc + else: + decorated.__doc__ = fn.__doc__ + decorated.__module__ = "sqlalchemy" + location.rsplit(".", 1)[0] + if decorated.__module__ not in sys.modules: + raise ImportError( + "public_factory location %s is not in sys.modules" + % (decorated.__module__,) + ) if compat.py2k or hasattr(fn, "__func__"): fn.__func__.__doc__ = doc + if not hasattr(fn.__func__, "_linked_to"): + fn.__func__._linked_to = (decorated, location) else: fn.__doc__ = doc + if not hasattr(fn, "_linked_to"): + fn._linked_to = (decorated, location) return decorated -- 2.47.2