temporalio/sdk-python

TypedSearchAttributes.__contains__ returns False for existing keys with falsy values

Summary

  • Context: The TypedSearchAttributes class in temporalio/common.py implements a collection-like interface for managing workflow search attributes with type-safe key-value pairs.

  • Bug: The __contains__ method incorrectly returns False when checking for keys that have falsy values (0, False, 0.0).

  • Actual vs. expected: The method evaluates the truthiness of the value instead of checking for key existence, violating Python’s in operator semantics which should return True whenever a key exists regardless of its value.

  • Impact: In temporalio/client.py, this causes attributes with falsy values to be incorrectly duplicated in both typed and untyped search attribute collections, leading to potential data corruption and query failures.

Code with bug

def __contains__(self, key: object) -> bool:
    """
    Check whether this search attribute contains the given key.
    This uses key equality so the key must be the same name and type.
    """
    return any(v for k, v in self if k == key)
    #          ^ <-- BUG 🔴 evaluates truthiness of value instead of key existence

The expression any(v for k, v in self if k == key) yields the value v for matching keys, then evaluates any() on those values. When a matching key has a falsy value, any() returns False despite the key existing.

Test demonstration

A simple test demonstrates the bug across all falsy value types:

def buggy_contains(search_attributes, key):
    """The buggy implementation"""
    return any(v for k, v in search_attributes if k == key)


def correct_contains(search_attributes, key):
    """The correct implementation"""
    return any(k == key for k, v in search_attributes)

Test results:

Test Case 1: Integer value of 0
----------------------------------------
Search attributes: [('int_key', 0)]

  Buggy implementation:    False
  Correct implementation:  True
  Expected:                True


Test Case 2: Boolean value of False
----------------------------------------
Search attributes: [('bool_key', False)]

  Buggy implementation:    False
  Correct implementation:  True
  Expected:                True


Test Case 3: Float value of 0.0
----------------------------------------
Search attributes: [('float_key', 0.0)]

  Buggy implementation:    False
  Correct implementation:  True
  Expected:                True


Test Case 4: Truthy value (control)
----------------------------------------
Search attributes: [('int_key', 42)]

The bug manifests consistently: when a key exists with a falsy value, the buggy implementation returns False while the correct implementation returns True.

Recommended fix

Change the implementation to check for key existence rather than value truthiness:

def __contains__(self, key: object) -> bool:
    """
    Check whether this search attribute contains the given key.
    This uses key equality so the key must be the same name and type.
    """
    return any(k == key for k, v in self)  # <-- FIX 🟢 checks key match, not value truthiness

This ensures the method returns True whenever a matching key is found, regardless of the associated value.

The bug affects production code in temporalio/client.py where the following logic depends on correct __contains__ behavior:

ntyped_not_in_typed = {
    k: v
    for k, v in self.untyped_search_attributes.items()
    if k not in self.typed_search_attributes  # <-- FIX 🟢 will work correctly after fix
}

After the fix, this code will correctly exclude attributes that exist in typed search attributes, even when they have falsy values.