From 27ec4929198807702190b96d3c00d0291976f49e Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 20 Jul 2021 11:03:08 -0400 Subject: [PATCH] dont warn for dictionary passed positionally Fixed issue where use of the :paramref:`_sql.case.whens` parameter passing a dictionary positionally and not as a keyword argument would emit a 2.0 deprecation warning, referring to the deprecation of passing a list positionally. The dictionary format of "whens", passed positionally, is still supported and was accidentally marked as deprecated. Removes warning filter for case statement. Fixes: #6786 Change-Id: I8efd1882563773bec89ae5e34f0dfede77fc4683 --- doc/build/changelog/unreleased_14/6786.rst | 10 ++++ lib/sqlalchemy/sql/coercions.py | 11 ++-- lib/sqlalchemy/sql/elements.py | 16 ++--- lib/sqlalchemy/testing/suite/test_select.py | 20 +++---- lib/sqlalchemy/testing/suite/test_types.py | 10 ++-- lib/sqlalchemy/testing/warnings.py | 1 - test/orm/test_merge.py | 4 +- test/orm/test_query.py | 2 +- test/orm/test_update_delete.py | 2 +- test/sql/test_case_statement.py | 8 +-- test/sql/test_deprecations.py | 66 ++++++++++++++++++--- test/sql/test_selectable.py | 2 +- 12 files changed, 101 insertions(+), 51 deletions(-) create mode 100644 doc/build/changelog/unreleased_14/6786.rst diff --git a/doc/build/changelog/unreleased_14/6786.rst b/doc/build/changelog/unreleased_14/6786.rst new file mode 100644 index 0000000000..7d95d5c928 --- /dev/null +++ b/doc/build/changelog/unreleased_14/6786.rst @@ -0,0 +1,10 @@ +.. change:: + :tags: bug, sql + :tickets: 6786 + + Fixed issue where use of the :paramref:`_sql.case.whens` parameter passing + a dictionary positionally and not as a keyword argument would emit a 2.0 + deprecation warning, referring to the deprecation of passing a list + positionally. The dictionary format of "whens", passed positionally, is + still supported and was accidentally marked as deprecated. + diff --git a/lib/sqlalchemy/sql/coercions.py b/lib/sqlalchemy/sql/coercions.py index e21f4a9a5f..dce3293524 100644 --- a/lib/sqlalchemy/sql/coercions.py +++ b/lib/sqlalchemy/sql/coercions.py @@ -97,11 +97,12 @@ def _document_text_coercion(paramname, meth_rst, param_rst): def _expression_collection_was_a_list(attrname, fnname, args): if args and isinstance(args[0], (list, set, dict)) and len(args) == 1: - util.warn_deprecated_20( - 'The "%s" argument to %s() is now passed as a series of ' - "positional " - "elements, rather than as a list. " % (attrname, fnname) - ) + if isinstance(args[0], list): + util.warn_deprecated_20( + 'The "%s" argument to %s(), when referring to a sequence ' + "of items, is now passed as a series of positional " + "elements, rather than as a list. " % (attrname, fnname) + ) return args[0] else: return args diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py index e253ddb936..ae93eb2a6c 100644 --- a/lib/sqlalchemy/sql/elements.py +++ b/lib/sqlalchemy/sql/elements.py @@ -2918,15 +2918,15 @@ class Case(ColumnElement): if "whens" in kw: util.warn_deprecated_20( - 'The "whens" argument to case() is now passed as a series of ' - "positional " - "elements, rather than as a list. " - ) - whens = kw.pop("whens") - else: - whens = coercions._expression_collection_was_a_list( - "whens", "case", whens + 'The "whens" argument to case() is now passed using ' + "positional style only, not as a keyword argument." ) + whens = (kw.pop("whens"),) + + whens = coercions._expression_collection_was_a_list( + "whens", "case", whens + ) + try: whens = util.dictlike_iteritems(whens) except TypeError: diff --git a/lib/sqlalchemy/testing/suite/test_select.py b/lib/sqlalchemy/testing/suite/test_select.py index 0a1227e584..c96c62a458 100644 --- a/lib/sqlalchemy/testing/suite/test_select.py +++ b/lib/sqlalchemy/testing/suite/test_select.py @@ -1259,12 +1259,10 @@ class ExpandingBoundInTest(fixtures.TablesTest): def test_null_in_empty_set_is_false_bindparam(self, connection): stmt = select( case( - [ - ( - null().in_(bindparam("foo", value=())), - true(), - ) - ], + ( + null().in_(bindparam("foo", value=())), + true(), + ), else_=false(), ) ) @@ -1273,12 +1271,10 @@ class ExpandingBoundInTest(fixtures.TablesTest): def test_null_in_empty_set_is_false_direct(self, connection): stmt = select( case( - [ - ( - null().in_([]), - true(), - ) - ], + ( + null().in_([]), + true(), + ), else_=false(), ) ) diff --git a/lib/sqlalchemy/testing/suite/test_types.py b/lib/sqlalchemy/testing/suite/test_types.py index f793ff5290..d367e7dc3c 100644 --- a/lib/sqlalchemy/testing/suite/test_types.py +++ b/lib/sqlalchemy/testing/suite/test_types.py @@ -362,12 +362,10 @@ class _DateFixture(_LiteralRoundTripFixture, fixtures.TestBase): id_ = result.inserted_primary_key[0] stmt = select(date_table.c.id).where( case( - [ - ( - bindparam("foo", type_=self.datatype) != None, - bindparam("foo", type_=self.datatype), - ) - ], + ( + bindparam("foo", type_=self.datatype) != None, + bindparam("foo", type_=self.datatype), + ), else_=date_table.c.date_data, ) == date_table.c.date_data diff --git a/lib/sqlalchemy/testing/warnings.py b/lib/sqlalchemy/testing/warnings.py index f8d6296a0c..af5ad184fb 100644 --- a/lib/sqlalchemy/testing/warnings.py +++ b/lib/sqlalchemy/testing/warnings.py @@ -74,7 +74,6 @@ def setup_filters(): "arguments in version 2.0", r"The Join.select\(\) method will no longer accept keyword arguments " "in version 2.0.", - r"The \"whens\" argument to case\(\) is now passed", r"The Join.select\(\).whereclause parameter is deprecated", # # DML diff --git a/test/orm/test_merge.py b/test/orm/test_merge.py index 22f0cd9ac3..a0cb5426f3 100644 --- a/test/orm/test_merge.py +++ b/test/orm/test_merge.py @@ -2086,13 +2086,13 @@ class PolymorphicOnTest(fixtures.MappedTest): self.classes.Employee, self.tables.employees, polymorphic_on=case( - value=self.tables.employees.c.type, - whens={ + { "E": "employee", "M": "manager", "G": "engineer", "R": "engineer", }, + value=self.tables.employees.c.type, ), polymorphic_identity="employee", ) diff --git a/test/orm/test_query.py b/test/orm/test_query.py index 07399030f0..3c806e9d5c 100644 --- a/test/orm/test_query.py +++ b/test/orm/test_query.py @@ -7290,7 +7290,7 @@ class SessionBindTest(QueryTest): User = self.classes.User session = fixture_session() with self._assert_bind_args(session, expect_mapped_bind=True): - session.query(case([(User.name == "x", "C")], else_="W")).all() + session.query(case((User.name == "x", "C"), else_="W")).all() def test_cast(self): User = self.classes.User diff --git a/test/orm/test_update_delete.py b/test/orm/test_update_delete.py index 9c8c4a5fc8..3890fd8bba 100644 --- a/test/orm/test_update_delete.py +++ b/test/orm/test_update_delete.py @@ -1721,7 +1721,7 @@ class UpdateDeleteFromTest(fixtures.MappedTest): # this would work with Firebird if you do literal_column('1') # instead - case_stmt = case([(Document.title.in_(subq), True)], else_=False) + case_stmt = case((Document.title.in_(subq), True), else_=False) s.query(Document).update( {"flag": case_stmt}, synchronize_session=False diff --git a/test/sql/test_case_statement.py b/test/sql/test_case_statement.py index 7dd66840f5..c6d5f0185b 100644 --- a/test/sql/test_case_statement.py +++ b/test/sql/test_case_statement.py @@ -175,8 +175,7 @@ class CaseTest(fixtures.TestBase, AssertsCompiledSQL): ), argnames="test_case, expected", ) - @testing.combinations(("positional",), ("kwarg",), argnames="argstyle") - def test_when_dicts(self, argstyle, test_case, expected): + def test_when_dicts(self, test_case, expected): t = table("test", column("col1")) whens, value, else_ = testing.resolve_lambda(test_case, t=t) @@ -188,10 +187,7 @@ class CaseTest(fixtures.TestBase, AssertsCompiledSQL): if else_ is not None: kw["else_"] = else_ - if argstyle == "kwarg": - return case(whens=whens, **kw) - elif argstyle == "positional": - return case(whens, **kw) + return case(whens, **kw) # note: 1.3 also does not allow this form # case([whens], **kw) diff --git a/test/sql/test_deprecations.py b/test/sql/test_deprecations.py index 7b2b6c57e7..3256ebcadf 100644 --- a/test/sql/test_deprecations.py +++ b/test/sql/test_deprecations.py @@ -605,7 +605,8 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL): t1 = table("t", column("q")) with testing.expect_deprecated( - r"The \"whens\" argument to case\(\) is now passed" + r"The \"whens\" argument to case\(\), when referring " + r"to a sequence of items, is now passed" ): stmt = select(t1).where( case( @@ -625,7 +626,8 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL): t1 = table("t", column("q")) with testing.expect_deprecated( - r"The \"whens\" argument to case\(\) is now passed" + r"The \"whens\" argument to case\(\), when referring " + "to a sequence of items, is now passed" ): stmt = select(t1).where( case( @@ -642,9 +644,56 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL): "ELSE :param_3 END != :param_4", ) + @testing.combinations( + ( + (lambda t: ({"x": "y"}, t.c.col1, None)), + "CASE test.col1 WHEN :param_1 THEN :param_2 END", + ), + ( + (lambda t: ({"x": "y", "p": "q"}, t.c.col1, None)), + "CASE test.col1 WHEN :param_1 THEN :param_2 " + "WHEN :param_3 THEN :param_4 END", + ), + ( + (lambda t: ({t.c.col1 == 7: "x"}, None, 10)), + "CASE WHEN (test.col1 = :col1_1) THEN :param_1 ELSE :param_2 END", + ), + ( + (lambda t: ({t.c.col1 == 7: "x", t.c.col1 == 10: "y"}, None, 10)), + "CASE WHEN (test.col1 = :col1_1) THEN :param_1 " + "WHEN (test.col1 = :col1_2) THEN :param_2 ELSE :param_3 END", + ), + argnames="test_case, expected", + ) + def test_when_kwarg(self, test_case, expected): + t = table("test", column("col1")) + + whens, value, else_ = testing.resolve_lambda(test_case, t=t) + + def _case_args(whens, value=None, else_=None): + kw = {} + if value is not None: + kw["value"] = value + if else_ is not None: + kw["else_"] = else_ + + with testing.expect_deprecated_20( + r'The "whens" argument to case\(\) is now passed using ' + r"positional style only, not as a keyword argument." + ): + + return case(whens=whens, **kw) + + # note: 1.3 also does not allow this form + # case([whens], **kw) + + self.assert_compile( + _case_args(whens=whens, value=value, else_=else_), + expected, + ) + def test_case_whens_dict_kw(self): t1 = table("t", column("q")) - with testing.expect_deprecated( r"The \"whens\" argument to case\(\) is now passed" ): @@ -655,7 +704,6 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL): ) != "bat" ) - self.assert_compile( stmt, "SELECT t.q FROM t WHERE CASE WHEN (t.q = :q_1) THEN " @@ -685,9 +733,10 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL): ) s1 = table1.select().scalar_subquery() - with testing.expect_deprecated( + with testing.expect_deprecated_20( r"The \"columns\" argument to " - r"Select.with_only_columns\(\) is now passed" + r"Select.with_only_columns\(\), when referring " + "to a sequence of items, is now passed" ): stmt = s1.with_only_columns([s1]) self.assert_compile( @@ -702,9 +751,10 @@ class SelectableTest(fixtures.TestBase, AssertsCompiledSQL): s1 = select(table1.c.a, table2.c.b) self.assert_compile(s1, "SELECT t1.a, t2.b FROM t1, t2") - with testing.expect_deprecated( + with testing.expect_deprecated_20( r"The \"columns\" argument to " - r"Select.with_only_columns\(\) is now passed" + r"Select.with_only_columns\(\), when referring " + "to a sequence of items, is now passed" ): s2 = s1.with_only_columns([table2.c.b]) diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index cfdf4ad02e..61e25ddd71 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -3137,7 +3137,7 @@ class ReprTest(fixtures.TestBase): elements.BooleanClauseList._construct_raw(operators.and_), elements.BooleanClauseList._construct_raw(operators.or_), elements.Tuple(), - elements.Case([]), + elements.Case(), elements.Extract("foo", column("x")), elements.UnaryExpression(column("x")), elements.Grouping(column("x")), -- 2.47.2