--- /dev/null
+.. 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.
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']))
# 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_>`::
in_([('ed', 'edsnickname'), ('wendy', 'windy')])
)
-* :meth:`NOT IN <.ColumnOperators.notin_>`::
+* :meth:`NOT IN <.ColumnOperators.not_in>`::
query.filter(~User.name.in_(['ed', 'wendy', 'jack']))
# 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_>`::
_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(
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)
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 ",
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"
)
"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,),
"""
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
: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.
: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.
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
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.
@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):
@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):
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,
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)
)
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)
)
)
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),
)
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),
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_
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_
@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()),
(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",
)
)
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")