Skip to content

Commit 2581c70

Browse files
refactor catalog loading again
1 parent f0ff7f4 commit 2581c70

File tree

2 files changed

+47
-17
lines changed

2 files changed

+47
-17
lines changed

src/pygambit/catalog/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,13 @@
66
def __getattr__(name: str):
77
"""Lazy load catalog games on access."""
88
from . import catalog as _catalog_module
9-
return getattr(_catalog_module, name)
9+
10+
# Don't try to load if already initializing to prevent recursion
11+
if not _catalog_module._catalog_initializing:
12+
# Ensure all catalog games are loaded
13+
_catalog_module._ensure_catalog_loaded()
14+
15+
if hasattr(_catalog_module, name):
16+
return getattr(_catalog_module, name)
17+
18+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

src/pygambit/catalog/catalog.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ def __init_subclass__(cls, **kwargs):
4747
if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib):
4848
return
4949

50-
# Load game and extract metadata immediately when class is defined
51-
cls.game = cls._game()
52-
cls._extract_description(cls.game)
53-
5450

5551
class CatalogGameFromContrib(CatalogGame):
5652
"""
@@ -84,13 +80,9 @@ def _load_game(cls) -> Game:
8480
raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{game_type}'")
8581

8682
def __init_subclass__(cls, **kwargs):
87-
"""Validate and extract metadata when subclass is defined."""
83+
"""Validate metadata when subclass is defined."""
8884
super().__init_subclass__(**kwargs)
8985

90-
# Load game and extract metadata immediately when class is defined
91-
cls.game = cls._load_game()
92-
cls._extract_description(cls.game)
93-
9486

9587
def games(
9688
num_actions: int | None = None,
@@ -144,8 +136,8 @@ def games(
144136
>>> games(x=1) # Games with a custom metadata field 'x' equal to 1
145137
>>> games(is_tree=True, num_players=2) # 2-player extensive-form games
146138
"""
147-
# Import manually coded games to ensure they are registered in the catalog
148-
_load_coded_games()
139+
# Ensure all games are loaded
140+
_ensure_catalog_loaded()
149141

150142
# Filter by extensive-form if filtering by tree-specific attributes
151143
if (
@@ -165,6 +157,19 @@ def get_all_subclasses(cls):
165157
all_subclasses.extend(get_all_subclasses(subclass))
166158
continue
167159

160+
# Ensure game is instantiated before filtering
161+
if subclass.game is None:
162+
try:
163+
if issubclass(subclass, CatalogGameFromContrib):
164+
subclass.game = subclass._load_game()
165+
else:
166+
subclass.game = subclass._game()
167+
subclass._extract_description(subclass.game)
168+
except Exception:
169+
# Skip games that fail to load
170+
all_subclasses.extend(get_all_subclasses(subclass))
171+
continue
172+
168173
if is_tree is not None and is_tree != subclass.game.is_tree:
169174
all_subclasses.extend(get_all_subclasses(subclass))
170175
continue
@@ -250,6 +255,27 @@ def get_all_subclasses(cls):
250255

251256
_CATALOG_YAML = Path(__file__).parent / "catalog.yml"
252257

258+
_catalog_initialized = False
259+
_catalog_initializing = False
260+
261+
262+
def _ensure_catalog_loaded():
263+
"""Ensure both YAML and coded games are loaded."""
264+
global _catalog_initialized, _catalog_initializing
265+
if _catalog_initialized:
266+
return
267+
if _catalog_initializing:
268+
return # Already loading, prevent re-entrance
269+
270+
_catalog_initializing = True
271+
try:
272+
_catalog_data = _load_catalog_from_yaml(_CATALOG_YAML)
273+
_generate_contrib_game_classes(_catalog_data)
274+
_load_coded_games()
275+
_catalog_initialized = True
276+
finally:
277+
_catalog_initializing = False
278+
253279

254280
def _load_catalog_from_yaml(path: Path) -> dict[str, dict]:
255281
if not path.exists():
@@ -315,8 +341,3 @@ def _load_coded_games():
315341
globals()[name] = obj
316342

317343
_coded_games_loaded = True
318-
319-
320-
# Generate classes at import time
321-
_catalog_data = _load_catalog_from_yaml(_CATALOG_YAML)
322-
_generate_contrib_game_classes(_catalog_data)

0 commit comments

Comments
 (0)