Skip to content

Comments

feat(gds-games): add discover_patterns() registry for auto-discovery of Pattern objects#24

Merged
rororowyourboat merged 2 commits intomainfrom
copilot/add-pattern-discovery-utility
Feb 24, 2026
Merged

feat(gds-games): add discover_patterns() registry for auto-discovery of Pattern objects#24
rororowyourboat merged 2 commits intomainfrom
copilot/add-pattern-discovery-utility

Conversation

Copy link
Contributor

Copilot AI commented Feb 24, 2026

Projects using gds-games repeatedly re-implement importlib.util boilerplate in every test module and use fragile sys.path.insert() hacks in application files to load pattern modules. This adds a single registry utility that eliminates both.

Changes

  • ogs/registry.py (new) — discover_patterns(directory, attribute="pattern") -> dict[str, Pattern]

    • Globs *.py, skipping _-prefixed files and __init__.py
    • Dynamically imports via importlib.util; silently skips files that fail to import or lack the attribute
    • Registers modules in sys.modules so intra-project relative imports resolve correctly
    • Raises NotADirectoryError for missing or non-directory paths
    • Returns dict[str, Pattern] in alphabetical order
  • ogs/__init__.py — exports discover_patterns at package top-level

Usage

# Before — repeated in every test module
def _load_pattern(name):
    path = PATTERNS_DIR / f"{name}.py"
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    sys.modules[name] = mod
    spec.loader.exec_module(mod)
    return mod.pattern

@pytest.mark.parametrize("name", ["reactive_decision", "bilateral_negotiation", ...])
def test_compile(name):
    pattern = _load_pattern(name)
    ...

# After
from ogs import discover_patterns

ALL_PATTERNS = discover_patterns(PATTERNS_DIR)

@pytest.mark.parametrize("name,pattern", ALL_PATTERNS.items())
def test_compile(name, pattern):
    ...
Original prompt

This section details on the original issue you should resolve

<issue_title>feat(gds-games): discover_patterns() — auto-discovery of Pattern objects from a directory</issue_title>
<issue_description>## Summary

Projects using gds-games typically organize patterns in a patterns/ directory and applications in an applications/ directory, each module exposing a top-level pattern variable. Currently:

  • Application files use sys.path.insert(0, project_root) to import from sibling directories — a fragile hack that pollutes sys.path globally
  • Test files re-implement the same ~20-line importlib.util loading pattern in every test module to dynamically load pattern modules by name

Both are solved by a small registry utility that belongs in the framework itself.

Proposed API

New module: packages/gds-games/ogs/registry.py

from pathlib import Path
from ogs.dsl.pattern import Pattern

def discover_patterns(
    directory: str | Path,
    attribute: str = "pattern",
) -> dict[str, Pattern]:
    """
    Scan a directory for Python files that expose a Pattern object.

    For each .py file (excluding __init__.py and files starting with _),
    attempts to import the module and retrieve the named attribute.
    Files that don't expose the attribute are silently skipped.

    Args:
        directory: Path to scan (absolute or relative to cwd)
        attribute: Name of the module-level variable to look for (default: "pattern")

    Returns:
        Dict mapping module stem name → Pattern object, in filesystem order.

    Example:
        patterns = discover_patterns("./patterns")
        apps     = discover_patterns("./applications")
        
        for name, pattern in patterns.items():
            ir = compile_to_ir(pattern)
            report = verify(ir)
    """

Implementation sketch

import importlib.util
import sys
from pathlib import Path
from ogs.dsl.pattern import Pattern

def discover_patterns(directory: str | Path, attribute: str = "pattern") -> dict[str, Pattern]:
    directory = Path(directory).resolve()
    result = {}
    for path in sorted(directory.glob("*.py")):
        if path.stem.startswith("_"):
            continue
        spec = importlib.util.spec_from_file_location(path.stem, path)
        if spec is None or spec.loader is None:
            continue
        mod = importlib.util.module_from_spec(spec)
        sys.modules[path.stem] = mod
        try:
            spec.loader.exec_module(mod)
        except Exception:
            continue
        obj = getattr(mod, attribute, None)
        if isinstance(obj, Pattern):
            result[path.stem] = obj
    return result

Files affected

  • packages/gds-games/ogs/registry.py — new module
  • packages/gds-games/ogs/__init__.py — export discover_patterns

Usage impact

Eliminates:

  • sys.path.insert() calls in application files that need to import from patterns/
  • Repeated importlib.util boilerplate in test files
  • Any manual enumeration of pattern names in test parametrize decorators
# Before (in test files — repeated in every test module)
def _load_pattern(name: str):
    path = PATTERNS_DIR / f"{name}.py"
    spec = importlib.util.spec_from_file_location(name, path)
    mod = importlib.util.module_from_spec(spec)
    sys.modules[name] = mod
    spec.loader.exec_module(mod)
    return mod.pattern

@pytest.mark.parametrize("name", ["reactive_decision", "bilateral_negotiation", ...])
def test_compile(name):
    pattern = _load_pattern(name)
    ...

# After
from ogs import discover_patterns

ALL_PATTERNS = discover_patterns(PATTERNS_DIR)

@pytest.mark.parametrize("name,pattern", ALL_PATTERNS.items())
def test_compile(name, pattern):
    ...

Effort estimate

Low — ~30 lines, mostly the importlib.util machinery already written in client test files.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Add auto-discovery of Pattern objects from patterns directory feat(gds-games): add discover_patterns() registry for auto-discovery of Pattern objects Feb 24, 2026
@rororowyourboat rororowyourboat marked this pull request as ready for review February 24, 2026 21:07
Copilot AI review requested due to automatic review settings February 24, 2026 21:07
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@rororowyourboat rororowyourboat merged commit 8b30893 into main Feb 24, 2026
9 checks passed
@rororowyourboat rororowyourboat deleted the copilot/add-pattern-discovery-utility branch February 24, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(gds-games): discover_patterns() — auto-discovery of Pattern objects from a directory

2 participants