This is an automated email from the ASF dual-hosted git repository.

skrawcz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/burr.git

commit 241d6ee0c7467841ab27f905dc26d93f2b9976c2
Author: Stefan Krawczyk <[email protected]>
AuthorDate: Sun Mar 15 22:10:39 2026 -0700

    Add is/isnot identity operators to when() conditions
    
    Adds __is and __isnot operators for identity checks, useful for
    None/True/False comparisons (e.g. when(value__is=None)).
---
 burr/core/action.py           |  7 +++++++
 docs/concepts/transitions.rst |  4 ++++
 tests/core/test_action.py     | 18 +++++++++++++++++-
 3 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/burr/core/action.py b/burr/core/action.py
index cd98558c..df15b7f0 100644
--- a/burr/core/action.py
+++ b/burr/core/action.py
@@ -433,6 +433,8 @@ class Condition(Function):
         "in": ("in", lambda a, b: a in b),
         "notin": ("not in", lambda a, b: a not in b),
         "contains": ("contains", lambda a, b: b in a),
+        "is": ("is", lambda a, b: a is b),
+        "isnot": ("is not", lambda a, b: a is not b),
     }
 
     @classmethod
@@ -482,6 +484,11 @@ class Condition(Function):
             when(status__notin=["x", "y"])  # state["status"] not in ["x", "y"]
             when(tags__contains="python")   # "python" in state["tags"]
 
+        Identity operators::
+
+            when(value__is=None)            # state["value"] is None
+            when(value__isnot=None)         # state["value"] is not None
+
         Multiple conditions are ANDed together::
 
             when(age__gte=18, status="active")  # age >= 18 AND status == 
"active"
diff --git a/docs/concepts/transitions.rst b/docs/concepts/transitions.rst
index 3ade8ef0..479ee881 100644
--- a/docs/concepts/transitions.rst
+++ b/docs/concepts/transitions.rst
@@ -67,6 +67,8 @@ Conditions have a few APIs, but the most common are the three 
convenience functi
         ("check", "tagged", when(tags__contains="python")),  # collection 
contains value
         ("check", "clean", when(status__notin=["banned", "suspended"])),  # 
not in
         ("check", "changed", when(status__ne="initial")),  # not equal
+        ("check", "missing", when(value__is=None)),  # identity check
+        ("check", "present", when(value__isnot=None)),  # not-identity check
     )
 
 Available operators:
@@ -81,6 +83,8 @@ Available operators:
 - ``key__in=[values]`` — value is in the given collection
 - ``key__notin=[values]`` — value is not in the given collection
 - ``key__contains=value`` — collection/string in state contains the value
+- ``key__is=value`` — identity check (``is``), useful for 
``None``/``True``/``False``
+- ``key__isnot=value`` — negated identity check (``is not``)
 
 Multiple keyword arguments are ANDed together. For more complex expressions, 
use ``expr()``.
 
diff --git a/tests/core/test_action.py b/tests/core/test_action.py
index 381bb6ff..f65a0683 100644
--- a/tests/core/test_action.py
+++ b/tests/core/test_action.py
@@ -166,6 +166,14 @@ def test_condition_when_complex():
         ({"tags__contains": "go"}, {"tags": ["python", "java"]}, False),
         ({"text__contains": "hello"}, {"text": "say hello world"}, True),
         ({"text__contains": "goodbye"}, {"text": "say hello world"}, False),
+        # __is (identity)
+        ({"value__is": None}, {"value": None}, True),
+        ({"value__is": None}, {"value": 0}, False),
+        ({"value__is": True}, {"value": True}, True),
+        ({"value__is": True}, {"value": 1}, False),
+        # __isnot (not identity)
+        ({"value__isnot": None}, {"value": "hello"}, True),
+        ({"value__isnot": None}, {"value": None}, False),
     ],
     ids=[
         "eq-match",
@@ -193,6 +201,12 @@ def test_condition_when_complex():
         "contains-list-no-match",
         "contains-str-match",
         "contains-str-no-match",
+        "is-none-match",
+        "is-none-no-match",
+        "is-true-match",
+        "is-true-not-identical",
+        "isnot-not-none",
+        "isnot-is-none",
     ],
 )
 def test_condition_when_operators(kwargs, state_dict, expected):
@@ -226,11 +240,13 @@ def test_condition_when_operators_reads(kwargs, 
expected_reads):
         ({"status__in": ["a", "b"]}, "status in ['a', 'b']"),
         ({"status__notin": ["x"]}, "status not in ['x']"),
         ({"tags__contains": "py"}, "tags contains 'py'"),
+        ({"value__is": None}, "value is None"),
+        ({"value__isnot": None}, "value is not None"),
         # plain equality still uses old format
         ({"foo": "bar"}, "foo=bar"),
         ({"foo": "bar", "baz": "qux"}, "baz=qux, foo=bar"),
     ],
-    ids=["gte", "lt", "ne", "in", "notin", "contains", "plain-eq", 
"plain-multi"],
+    ids=["gte", "lt", "ne", "in", "notin", "contains", "is", "isnot", 
"plain-eq", "plain-multi"],
 )
 def test_condition_when_operators_name(kwargs, expected_name):
     cond = Condition.when(**kwargs)

Reply via email to