Skip to content

Commit 0854c39

Browse files
committed
Handle unhashable entries in sys.modules when collecting local constants
Some packages (e.g. cog) place unhashable objects like SimpleNamespace in sys.modules. This caused a TypeError when _get_local_constants tried to build a set from sys.modules.values(). Fix by catching TypeError on the membership check and falling back to tracking by module name (the str key in sys.modules) for unhashable entries. https://claude.ai/code/session_01PQkqWu11828t7hwzAyR1sZ
1 parent fefce87 commit 0854c39

2 files changed

Lines changed: 21 additions & 8 deletions

File tree

hypothesis-python/RELEASE.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
RELEASE_TYPE: patch
2+
3+
This patch fixes a crash when :obj:`sys.modules` contains unhashable values,
4+
such as ``types.SimpleNamespace`` objects (:issue:`4660`).

hypothesis-python/src/hypothesis/internal/conjecture/providers.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from functools import cached_property
1919
from random import Random
2020
from sys import float_info
21-
from types import ModuleType
2221
from typing import (
2322
TYPE_CHECKING,
2423
Any,
@@ -274,7 +273,9 @@
274273
# are all modules, not necessarily local ones. This lets us quickly see which
275274
# modules are new without an expensive path.resolve() or is_local_module_file
276275
# cache lookup.
277-
_seen_modules: set[ModuleType] = set()
276+
# We track by module object when hashable, falling back to the module name
277+
# (str key in sys.modules) for unhashable entries like SimpleNamespace.
278+
_seen_modules: set = set()
278279
_sys_modules_len: int | None = None
279280

280281

@@ -310,20 +311,28 @@ def _get_local_constants() -> Constants:
310311
# careful: store sys.modules length when we first check to avoid race conditions
311312
# with other threads loading a module before we set _sys_modules_len.
312313
if (sys_modules_len := len(sys.modules)) != _sys_modules_len:
313-
# set(_seen_modules) shouldn't typically be required, but I have run into
314-
# a "set changed size during iteration" error here when running
315-
# test_provider_conformance_crosshair.
316-
new_modules = set(sys.modules.values()) - set(_seen_modules)
314+
new_modules = []
315+
for name, module in sys.modules.items():
316+
try:
317+
seen = module in _seen_modules
318+
except TypeError:
319+
# unhashable module (e.g. SimpleNamespace); fall back to name
320+
seen = name in _seen_modules
321+
if not seen:
322+
new_modules.append((name, module))
317323
# Repeated SortedSet unions are expensive. Do the initial unions on a
318324
# set(), then do a one-time union with _local_constants after.
319325
new_constants = Constants()
320-
for module in new_modules:
326+
for name, module in new_modules:
321327
if (
322328
module_file := getattr(module, "__file__", None)
323329
) is not None and is_local_module_file(module_file):
324330
new_constants |= constants_from_module(module)
331+
try:
332+
_seen_modules.add(module)
333+
except TypeError:
334+
_seen_modules.add(name)
325335
_local_constants |= new_constants
326-
_seen_modules.update(new_modules)
327336
_sys_modules_len = sys_modules_len
328337

329338
# if we add any new constant, invalidate the constant cache for permitted values.

0 commit comments

Comments
 (0)