Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cuda_core/cuda/core/utils/_program_cache/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,21 @@ class ProgramCacheResource(abc.ABC):
``__contains__`` invites that pattern. ``get`` answers
both questions in one filesystem-level operation, so a
successful return always carries the bytes.

``key in cache`` therefore raises ``TypeError``, and a cache
is not iterable.
"""

# Opt out of the legacy sequence-iteration protocol. Defining
# ``__getitem__`` without ``__iter__`` makes ``key in cache`` fall back to
# ``cache[0], cache[1], ...`` compared against the *values* -- which
# defeats the "no ``__contains__``" design above: on these backends it
# raises a baffling "cache keys must be bytes or str, got int", and on a
# subclass whose ``__getitem__`` accepts integers it silently answers about
# values instead of keys. ``__iter__ = None`` restores the plain
# ``TypeError: argument of type '...' is not iterable``.
__iter__ = None

@abc.abstractmethod
def __getitem__(self, key: bytes | str) -> bytes:
"""Retrieve the cached binary bytes.
Expand Down
8 changes: 8 additions & 0 deletions cuda_core/docs/source/release/1.2.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ Fixes and enhancements
Windows, both ``ctypes.CFUNCTYPE`` and ``ctypes.WINFUNCTYPE`` are accepted.
(`#2439 <https://github.com/NVIDIA/cuda-python/issues/2439>`__)

- ``key in cache`` on a :class:`~cuda.core.utils.ProgramCacheResource` now
raises ``TypeError`` instead of walking the cache as a legacy sequence.
The class deliberately provides no ``__contains__``, but defining
``__getitem__`` without ``__iter__`` made ``in`` fall back to
``cache[0], cache[1], ...`` compared against the values. Use
:meth:`~cuda.core.utils.ProgramCacheResource.get`, which answers presence and
retrieval in one operation.

Deprecation Notices
-------------------

Expand Down
31 changes: 31 additions & 0 deletions cuda_core/tests/test_program_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,37 @@ def test_program_cache_resource_requires_core_methods():
assert "__contains__" not in ProgramCacheResource.__abstractmethods__


@pytest.mark.agent_authored(model="claude-opus-5")
@pytest.mark.parametrize("factory", ["inmemory", "filestream"])
def test_program_cache_is_not_iterable_and_rejects_in(tmp_path, factory):
"""The ABC documents having no ``__contains__``, but defining
``__getitem__`` without ``__iter__`` handed ``key in cache`` to the legacy
sequence protocol: it walked ``cache[0], cache[1], ...`` and compared
against the *values*. On these backends that surfaced as a baffling
"cache keys must be bytes or str, got int"; on a subclass whose
``__getitem__`` accepts integers it silently answered about values instead
of keys.
"""
from cuda.core.utils import FileStreamProgramCache, InMemoryProgramCache

cache = InMemoryProgramCache() if factory == "inmemory" else FileStreamProgramCache(tmp_path / "fc")
with cache:
cache[b"k"] = b"v"

with pytest.raises(TypeError, match="not iterable"):
b"k" in cache # noqa: B015
with pytest.raises(TypeError, match="not iterable"):
iter(cache)
with pytest.raises(TypeError, match="not iterable"):
list(cache)

# The supported lookups are unaffected.
assert cache[b"k"] == b"v"
assert cache.get(b"k") == b"v"
assert cache.get(b"absent") is None
assert len(cache) == 1


def _build_empty_subclass():
from cuda.core.utils import ProgramCacheResource

Expand Down
Loading