]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Rename Core expression isnot, not_in_
authorjonathan vanasco <jonathan@2xlp.com>
Tue, 1 Sep 2020 20:56:53 +0000 (16:56 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 14 Sep 2020 15:34:49 +0000 (11:34 -0400)
Several operators are renamed to achieve more consistent naming across
SQLAlchemy.

The operator changes are:

* `isnot` is now `is_not`
* `not_in_` is now `not_in`

Because these are core operators, the internal migration strategy for this
change is to support legacy terms for an extended period of time -- if not
indefinitely -- but update all documentation, tutorials, and internal usage
to the new terms.  The new terms are used to define the functions, and
the legacy terms have been deprecated into aliases of the new terms.

Fixes: #5429
Change-Id: Ia1e66e7a50ac35d3f6260d8bf6ba3ce8087cbad2

doc/build/changelog/unreleased_14/5429.rst [new file with mode: 0644]
doc/build/core/tutorial.rst
doc/build/orm/tutorial.rst
lib/sqlalchemy/orm/evaluator.py
lib/sqlalchemy/sql/compiler.py
lib/sqlalchemy/sql/default_comparator.py
lib/sqlalchemy/sql/operators.py
lib/sqlalchemy/testing/suite/test_select.py
test/orm/test_evaluator.py
test/sql/test_deprecations.py
test/sql/test_operators.py

diff --git a/doc/build/changelog/unreleased_14/5429.rst b/doc/build/changelog/unreleased_14/5429.rst
new file mode 100644 (file)
index 0000000..2ff7e24
--- /dev/null
@@ -0,0 +1,17 @@
+.. change::
+    :tags: change, sql
+    :tickets: 5429
+
+    Several operators are renamed to achieve more consistent naming across
+    SQLAlchemy.
+
+    The operator changes are:
+
+    * `isnot` is now `is_not`
+    * `not_in_` is now `not_in`
+
+    Because these are core operators, the internal migration strategy for this
+    change is to support legacy terms for an extended period of time -- if not
+    indefinitely -- but update all documentation, tutorials, and internal usage
+    to the new terms.  The new terms are used to define the functions, and
+    the legacy terms have been deprecated into aliases of the new terms.
index 738d4d74ee63229a40a4bfce82614eb4e23d2ff3..395144ed1d20923fd747a7bc10b5c1a9fe9c8ec8 100644 (file)
@@ -775,7 +775,7 @@ objects is at :class:`.ColumnOperators`.
         in_([('ed', 'edsnickname'), ('wendy', 'windy')])
     )
 
-* :meth:`NOT IN <.ColumnOperators.notin_>`::
+* :meth:`NOT IN <.ColumnOperators.not_in>`::
 
     statement.where(~users.c.name.in_(['ed', 'wendy', 'jack']))
 
@@ -786,12 +786,12 @@ objects is at :class:`.ColumnOperators`.
     # alternatively, if pep8/linters are a concern
     statement.where(users.c.name.is_(None))
 
-* :meth:`IS NOT NULL <.ColumnOperators.isnot>`::
+* :meth:`IS NOT NULL <.ColumnOperators.is_not>`::
 
     statement.where(users.c.name != None)
 
     # alternatively, if pep8/linters are a concern
-    statement.where(users.c.name.isnot(None))
+    statement.where(users.c.name.is_not(None))
 
 * :func:`AND <.sql.expression.and_>`::
 
index c04caf9e6fc8cff7b2df83b3ec81df2e6f39b5cc..5ed42449ac3b4f7c831b3e70abd0f5c74dc107a7 100644 (file)
@@ -789,7 +789,7 @@ Here's a rundown of some of the most common operators used in
         in_([('ed', 'edsnickname'), ('wendy', 'windy')])
     )
 
-* :meth:`NOT IN <.ColumnOperators.notin_>`::
+* :meth:`NOT IN <.ColumnOperators.not_in>`::
 
     query.filter(~User.name.in_(['ed', 'wendy', 'jack']))
 
@@ -800,12 +800,12 @@ Here's a rundown of some of the most common operators used in
     # alternatively, if pep8/linters are a concern
     query.filter(User.name.is_(None))
 
-* :meth:`IS NOT NULL <.ColumnOperators.isnot>`::
+* :meth:`IS NOT NULL <.ColumnOperators.is_not>`::
 
     query.filter(User.name != None)
 
     # alternatively, if pep8/linters are a concern
-    query.filter(User.name.isnot(None))
+    query.filter(User.name.is_not(None))
 
 * :func:`AND <.sql.expression.and_>`::
 
index 21d5e72d4088df7baaccc50b211adf1693bc7265..f7f12ce127210aee7340b8bbc3ea68dbb5c0792e 100644 (file)
@@ -37,7 +37,7 @@ _straight_ops = set(
 
 _extended_ops = {
     operators.in_op: (lambda a, b: a in b),
-    operators.notin_op: (lambda a, b: a not in b),
+    operators.not_in_op: (lambda a, b: a not in b),
 }
 
 _notimplemented_ops = set(
@@ -170,7 +170,7 @@ class EvaluatorCompiler(object):
             def evaluate(obj):
                 return eval_left(obj) == eval_right(obj)
 
-        elif operator is operators.isnot:
+        elif operator is operators.is_not:
 
             def evaluate(obj):
                 return eval_left(obj) != eval_right(obj)
index ec1a5793554bcd09fb19283e568e6260f36fb8d1..9254415392a3804e748c62d1210f92aa45d0b478 100644 (file)
@@ -190,12 +190,12 @@ OPERATORS = {
     operators.match_op: " MATCH ",
     operators.notmatch_op: " NOT MATCH ",
     operators.in_op: " IN ",
-    operators.notin_op: " NOT IN ",
+    operators.not_in_op: " NOT IN ",
     operators.comma_op: ", ",
     operators.from_: " FROM ",
     operators.as_: " AS ",
     operators.is_: " IS ",
-    operators.isnot: " IS NOT ",
+    operators.is_not: " IS NOT ",
     operators.collate: " COLLATE ",
     # unary
     operators.exists: "EXISTS ",
index eec174e8b55667ee5713e74f41e0ba163ea9c04f..d5762ff1f3e236c41fc85df345fd25726bf0c848 100644 (file)
@@ -73,20 +73,20 @@ def _boolean_compare(
                     expr,
                     coercions.expect(roles.ConstExprRole, obj),
                     operators.is_,
-                    negate=operators.isnot,
+                    negate=operators.is_not,
                     type_=result_type,
                 )
-            elif op in (operators.ne, operators.isnot):
+            elif op in (operators.ne, operators.is_not):
                 return BinaryExpression(
                     expr,
                     coercions.expect(roles.ConstExprRole, obj),
-                    operators.isnot,
+                    operators.is_not,
                     negate=operators.is_,
                     type_=result_type,
                 )
             else:
                 raise exc.ArgumentError(
-                    "Only '=', '!=', 'is_()', 'isnot()', "
+                    "Only '=', '!=', 'is_()', 'is_not()', "
                     "'is_distinct_from()', 'isnot_distinct_from()' "
                     "operators can be used with None/True/False"
                 )
@@ -328,10 +328,10 @@ operator_lookup = {
     "asc_op": (_scalar, UnaryExpression._create_asc),
     "nullsfirst_op": (_scalar, UnaryExpression._create_nullsfirst),
     "nullslast_op": (_scalar, UnaryExpression._create_nullslast),
-    "in_op": (_in_impl, operators.notin_op),
-    "notin_op": (_in_impl, operators.in_op),
+    "in_op": (_in_impl, operators.not_in_op),
+    "not_in_op": (_in_impl, operators.in_op),
     "is_": (_boolean_compare, operators.is_),
-    "isnot": (_boolean_compare, operators.isnot),
+    "is_not": (_boolean_compare, operators.is_not),
     "collate": (_collate_impl,),
     "match_op": (_match_impl,),
     "notmatch_op": (_match_impl,),
index ba03a6934708fec593df8d132139cde3dfbc1d00..5f5052c2862b65c4eccf460cc65421ec7f79c880 100644 (file)
@@ -596,7 +596,7 @@ class ColumnOperators(Operators):
         """
         return self.operate(in_op, other)
 
-    def notin_(self, other):
+    def not_in(self, other):
         """implement the ``NOT IN`` operator.
 
         This is equivalent to using negation with
@@ -608,8 +608,12 @@ class ColumnOperators(Operators):
         :paramref:`_sa.create_engine.empty_in_strategy` may be used to
         alter this behavior.
 
+        .. versionchanged:: 1.4 The ``not_in()`` operator is renamed from
+           ``notin_()`` in previous releases.  The previous name remains
+           available for backwards compatibility.
+
         .. versionchanged:: 1.2  The :meth:`.ColumnOperators.in_` and
-           :meth:`.ColumnOperators.notin_` operators
+           :meth:`.ColumnOperators.not_in` operators
            now produce a "static" expression for an empty IN sequence
            by default.
 
@@ -618,7 +622,10 @@ class ColumnOperators(Operators):
             :meth:`.ColumnOperators.in_`
 
         """
-        return self.operate(notin_op, other)
+        return self.operate(not_in_op, other)
+
+    # deprecated 1.4; see #5429
+    notin_ = not_in
 
     def notlike(self, other, escape=None):
         """implement the ``NOT LIKE`` operator.
@@ -654,12 +661,12 @@ class ColumnOperators(Operators):
         usage of ``IS`` may be desirable if comparing to boolean values
         on certain platforms.
 
-        .. seealso:: :meth:`.ColumnOperators.isnot`
+        .. seealso:: :meth:`.ColumnOperators.is_not`
 
         """
         return self.operate(is_, other)
 
-    def isnot(self, other):
+    def is_not(self, other):
         """Implement the ``IS NOT`` operator.
 
         Normally, ``IS NOT`` is generated automatically when comparing to a
@@ -667,10 +674,18 @@ class ColumnOperators(Operators):
         usage of ``IS NOT`` may be desirable if comparing to boolean values
         on certain platforms.
 
+        .. versionchanged:: 1.4 The ``is_not()`` operator is renamed from
+           ``isnot()`` in previous releases.  The previous name remains
+           available for backwards compatibility.
+
+
         .. seealso:: :meth:`.ColumnOperators.is_`
 
         """
-        return self.operate(isnot, other)
+        return self.operate(is_not, other)
+
+    # deprecated 1.4; see #5429
+    isnot = is_not
 
     def startswith(self, other, **kwargs):
         r"""Implement the ``startswith`` operator.
@@ -1269,8 +1284,12 @@ def is_(a, b):
 
 
 @comparison_op
-def isnot(a, b):
-    return a.isnot(b)
+def is_not(a, b):
+    return a.is_not(b)
+
+
+# 1.4 deprecated; see #5429
+isnot = is_not
 
 
 def collate(a, b):
@@ -1317,8 +1336,12 @@ def in_op(a, b):
 
 
 @comparison_op
-def notin_op(a, b):
-    return a.notin_(b)
+def not_in_op(a, b):
+    return a.not_in(b)
+
+
+# 1.4 deprecated; see #5429
+notin_op = not_in_op
 
 
 def distinct_op(a):
@@ -1529,9 +1552,9 @@ _PRECEDENCE = {
     like_op: 5,
     notlike_op: 5,
     in_op: 5,
-    notin_op: 5,
+    not_in_op: 5,
     is_: 5,
-    isnot: 5,
+    is_not: 5,
     eq: 5,
     ne: 5,
     is_distinct_from: 5,
index adcd7d8b90f29378050ca5d2bfcd7820a29c24af..c199929a7263b55db3f42fed5151a59085418862 100644 (file)
@@ -821,7 +821,7 @@ class ExpandingBoundInTest(fixtures.TablesTest):
 
         stmt = (
             select(table.c.id)
-            .where(table.c.x.notin_(bindparam("q", expanding=True)))
+            .where(table.c.x.not_in(bindparam("q", expanding=True)))
             .order_by(table.c.id)
         )
 
@@ -843,7 +843,7 @@ class ExpandingBoundInTest(fixtures.TablesTest):
 
         stmt = (
             select(table.c.id)
-            .where(table.c.z.notin_(bindparam("q", expanding=True)))
+            .where(table.c.z.not_in(bindparam("q", expanding=True)))
             .order_by(table.c.id)
         )
 
index 20577d8e62f353f009b232d77817fefbf6591779..a6c889aa742919b81cbfbc48e93bb1da6fcae559 100644 (file)
@@ -205,7 +205,7 @@ class EvaluateTest(fixtures.MappedTest):
         )
 
         eval_eq(
-            User.name.notin_(["foo", "bar"]),
+            User.name.not_in(["foo", "bar"]),
             testcases=[
                 (User(id=1, name="foo"), False),
                 (User(id=2, name="bat"), True),
@@ -229,7 +229,7 @@ class EvaluateTest(fixtures.MappedTest):
         )
 
         eval_eq(
-            tuple_(User.id, User.name).notin_([(1, "foo"), (2, "bar")]),
+            tuple_(User.id, User.name).not_in([(1, "foo"), (2, "bar")]),
             testcases=[
                 (User(id=1, name="foo"), False),
                 (User(id=2, name="bat"), True),
index edaa951e151314ae4c2f0dae36afcf1a531e3d48..a72974dbbef6d87f38aec1af54390bf33274c02e 100644 (file)
@@ -28,12 +28,14 @@ from sqlalchemy import util
 from sqlalchemy import VARCHAR
 from sqlalchemy.engine import default
 from sqlalchemy.sql import coercions
+from sqlalchemy.sql import operators
 from sqlalchemy.sql import quoted_name
 from sqlalchemy.sql import roles
 from sqlalchemy.sql import visitors
 from sqlalchemy.sql.selectable import SelectStatementGrouping
 from sqlalchemy.testing import assert_raises
 from sqlalchemy.testing import assert_raises_message
+from sqlalchemy.testing import assertions
 from sqlalchemy.testing import AssertsCompiledSQL
 from sqlalchemy.testing import engines
 from sqlalchemy.testing import eq_
@@ -1777,3 +1779,70 @@ class TableDeprecationTest(fixtures.TestBase):
                 exc.InvalidRequestError, "Table 'foo' not defined"
             ):
                 Table("foo", MetaData(), mustexist=True)
+
+
+class LegacyOperatorTest(AssertsCompiledSQL, fixtures.TestBase):
+    __dialect__ = "default"
+
+    def test_issue_5429_compile(self):
+        self.assert_compile(column("x").isnot("foo"), "x IS NOT :x_1")
+
+        self.assert_compile(
+            column("x").notin_(["foo", "bar"]), "x NOT IN ([POSTCOMPILE_x_1])"
+        )
+
+    def test_issue_5429_operators(self):
+        # functions
+        # is_not
+        assert hasattr(operators, "is_not")  # modern
+        assert hasattr(operators, "isnot")  # legacy
+        assert operators.is_not is operators.isnot
+        # not_in
+        assert hasattr(operators, "not_in_op")  # modern
+        assert hasattr(operators, "notin_op")  # legacy
+        assert operators.not_in_op is operators.notin_op
+
+        # precedence mapping
+        # is_not
+        assert operators.is_not in operators._PRECEDENCE  # modern
+        assert operators.isnot in operators._PRECEDENCE  # legacy
+        assert (
+            operators._PRECEDENCE[operators.is_not]
+            == operators._PRECEDENCE[operators.isnot]
+        )
+        # not_in_op
+        assert operators.not_in_op in operators._PRECEDENCE  # modern
+        assert operators.notin_op in operators._PRECEDENCE  # legacy
+        assert (
+            operators._PRECEDENCE[operators.not_in_op]
+            == operators._PRECEDENCE[operators.notin_op]
+        )
+
+        # ColumnOperators
+        # is_not
+        assert hasattr(operators.ColumnOperators, "is_not")  # modern
+        assert hasattr(operators.ColumnOperators, "isnot")  # legacy
+        assert (
+            operators.ColumnOperators.is_not == operators.ColumnOperators.isnot
+        )
+        # not_in
+        assert hasattr(operators.ColumnOperators, "not_in")  # modern
+        assert hasattr(operators.ColumnOperators, "notin_")  # legacy
+        assert (
+            operators.ColumnOperators.not_in
+            == operators.ColumnOperators.notin_
+        )
+
+    def test_issue_5429_assertions(self):
+        """
+        2) ensure compatibility across sqlalchemy.testing.assertions
+        """
+        # functions
+        # is_not
+        assert hasattr(assertions, "is_not")  # modern
+        assert hasattr(assertions, "is_not_")  # legacy
+        assert assertions.is_not is assertions.is_not_
+        # not_in
+        assert hasattr(assertions, "not_in")  # modern
+        assert hasattr(assertions, "not_in_")  # legacy
+        assert assertions.not_in is assertions.not_in_
index c011b961c870a873ed40ff9f7d56aac49b0eef95..3eb0c449f792f7f6512af8acd866c8c8788b39d8 100644 (file)
@@ -87,7 +87,8 @@ class DefaultColumnComparatorTest(fixtures.TestBase):
     @testing.combinations(
         (operators.add, right_column),
         (operators.is_, None),
-        (operators.isnot, None),
+        (operators.is_not, None),
+        (operators.isnot, None),  # deprecated 1.4; See #5429
         (operators.is_, null()),
         (operators.is_, true()),
         (operators.is_, false()),
@@ -98,15 +99,18 @@ class DefaultColumnComparatorTest(fixtures.TestBase):
         (operators.is_distinct_from, None),
         (operators.isnot_distinct_from, True),
         (operators.is_, True),
-        (operators.isnot, True),
+        (operators.is_not, True),
+        (operators.isnot, True),  # deprecated 1.4; See #5429
         (operators.is_, False),
-        (operators.isnot, False),
+        (operators.is_not, False),
+        (operators.isnot, False),  # deprecated 1.4; See #5429
         (operators.like_op, right_column),
         (operators.notlike_op, right_column),
         (operators.ilike_op, right_column),
         (operators.notilike_op, right_column),
         (operators.is_, right_column),
-        (operators.isnot, right_column),
+        (operators.is_not, right_column),
+        (operators.isnot, right_column),  # deprecated 1.4; See #5429
         (operators.concat_op, right_column),
         id_="ns",
     )
@@ -179,19 +183,19 @@ class DefaultColumnComparatorTest(fixtures.TestBase):
         )
         self._loop_test(operators.in_op, [1, 2, 3])
 
-    def test_notin(self):
+    def test_not_in(self):
         left = column("left")
-        assert left.comparator.operate(operators.notin_op, [1, 2, 3]).compare(
+        assert left.comparator.operate(operators.not_in_op, [1, 2, 3]).compare(
             BinaryExpression(
                 left,
                 BindParameter(
                     "left", value=[1, 2, 3], unique=True, expanding=True
                 ),
-                operators.notin_op,
+                operators.not_in_op,
                 type_=sqltypes.BOOLEANTYPE,
             )
         )
-        self._loop_test(operators.notin_op, [1, 2, 3])
+        self._loop_test(operators.not_in_op, [1, 2, 3])
 
     def test_in_no_accept_list_of_non_column_element(self):
         left = column("left")