cocoindex-io/cocoindex · error · TypeError

{type(self).__name__} cannot be used as a memoization key. T

Error message

{type(self).__name__} cannot be used as a memoization key. This type maintains internal state that is incompatible with memoization.

What it means

Types marked as NotMemoKeyable implement __coco_memo_key__ to deliberately raise TypeError when used as a memoization key, because they hold internal state (e.g. connection pools, handles) whose identity is not stable across runs and would produce incorrect memo hits.

Source

Thrown at python/cocoindex/_internal/memo_fingerprint.py:190

            for name in field_names
        ),
    )


class NotMemoKeyable:
    """
    Base class for objects that must not be used as memoization keys.

    Inherit from this class when an object maintains internal state that would
    make memoization semantically incorrect (e.g., generators that track call counts).

    Attempting to use a `NotMemoKeyable` instance as a memo key will raise TypeError.
    """

    __slots__ = ()

    def __coco_memo_key__(self) -> typing.NoReturn:
        raise TypeError(
            f"{type(self).__name__} cannot be used as a memoization key. "
            "This type maintains internal state that is incompatible with memoization."
        )


def register_memo_key_function(
    typ: type, key_fn: _KeyFn, *, state_fn: _StateFn | None = None
) -> None:
    """Register a memo key function for a type.

    Resolution is MRO-aware: the most specific registered base type wins.

    If *state_fn* is provided it is stored separately and used for memo state
    validation (see ``_canonicalize``).
    """

    _memo_fns[typ] = _MemoFns(key_fn, state_fn)

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Remove the stateful object from the memoized function's arguments; fetch it inside via coco.use_context(key).
  2. Turn off memoization (memo=False / default) if the argument is genuinely needed.
  3. Pass a stable key/identifier (str) instead of the live object itself.

Example fix

// before
@coco.fn(memo=True)
async def query(pool: Pool, sql: str): ...
// after
@coco.fn(memo=True)
async def query(sql: str) -> ...:
    pool = coco.use_context(PG_DB)
Defensive patterns

Strategy: type-guard

Validate before calling

def memo_safe(value) -> bool:
    return not hasattr(type(value), '__coco_memo_key__') or type(value).__coco_memo_key__ is not typing.NoReturn

Type guard

def is_memo_keyable(x) -> bool:
    try:
        x.__coco_memo_key__ if hasattr(x, '__coco_memo_key__') else True
        return True
    except TypeError:
        return False

Try / catch

try:
    result = await memoized_fn(resource, arg)
except TypeError as e:
    if "memoization key" in str(e):
        result = await memoized_fn(arg)  # fetch resource via use_context inside

Prevention

When it happens

Trigger: Passing an instance of a stateful type (one registered/defined as NotMemoKeyable) as an argument to a @coco.fn(memo=True) function, so fingerprinting invokes __coco_memo_key__.

Common situations: Accidentally passing a database pool, client handle, or environment object as a function argument to a memoized fn instead of obtaining it via coco.use_context; wrapping functions that take live resources.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/51da459cd3243c8c. Report an issue: GitHub.