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
10 changes: 10 additions & 0 deletions rdagent/components/coder/factor_coder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
from rdagent.components.coder.factor_coder._costeer_compat import (
install_compat_redirector,
)

# Re-route the pre-refactor `rdagent.components.coder.factor_coder.CoSTEER.*`
# import path to the new `rdagent.components.coder.CoSTEER.*` package so that
# pre-existing pickled traces (e.g. the `demo_traces` archive referenced in
# the README) continue to deserialise under `rdagent ui`. See #1331.
install_compat_redirector()

from rdagent.components.coder.CoSTEER import CoSTEER
from rdagent.components.coder.CoSTEER.evaluators import CoSTEERMultiEvaluator
from rdagent.components.coder.factor_coder.config import FACTOR_COSTEER_SETTINGS
Expand Down
61 changes: 61 additions & 0 deletions rdagent/components/coder/factor_coder/_costeer_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Backward-compatibility shim for the pre-refactor CoSTEER module path.

Traces pickled before the CoSTEER relocation reference
``rdagent.components.coder.factor_coder.CoSTEER.*`` (the old location).
After the refactor the package lives at ``rdagent.components.coder.CoSTEER.*``,
so loading those pickles in ``rdagent ui`` fails with::

ModuleNotFoundError: No module named
'rdagent.components.coder.factor_coder.CoSTEER'

— see #1331. Install a ``sys.meta_path`` finder that transparently redirects
imports under the old path to the matching submodule under the new one, so
the pre-existing demo traces continue to deserialise without anyone having
to roll back to the legacy commit.
"""

from __future__ import annotations

import importlib
import importlib.abc
import importlib.machinery
import importlib.util
import sys
from types import ModuleType
from typing import Optional, Sequence

_OLD_PREFIX = "rdagent.components.coder.factor_coder.CoSTEER"
_NEW_PREFIX = "rdagent.components.coder.CoSTEER"


class _CoSTEERPathRedirector(importlib.abc.MetaPathFinder, importlib.abc.Loader):
"""Resolve ``<OLD_PREFIX>[.submodule]`` imports against the new package."""

def find_spec(
self,
fullname: str,
path: Optional[Sequence[str]] = None,
target: Optional[ModuleType] = None,
) -> Optional[importlib.machinery.ModuleSpec]:
if fullname != _OLD_PREFIX and not fullname.startswith(_OLD_PREFIX + "."):
return None
new_name = _NEW_PREFIX + fullname[len(_OLD_PREFIX):]
target_module = importlib.import_module(new_name)
sys.modules[fullname] = target_module
spec = importlib.util.spec_from_loader(fullname, self, is_package=hasattr(target_module, "__path__"))
return spec

def create_module(self, spec: importlib.machinery.ModuleSpec) -> Optional[ModuleType]:
# The module has already been populated by find_spec via the redirect
# target, so we just hand it back to the import machinery.
return sys.modules.get(spec.name)

def exec_module(self, module: ModuleType) -> None:
# No-op: the redirect target is fully initialised already.
return None


def install_compat_redirector() -> None:
"""Install the redirector once; safe to call multiple times."""
if not any(isinstance(f, _CoSTEERPathRedirector) for f in sys.meta_path):
sys.meta_path.append(_CoSTEERPathRedirector())
74 changes: 74 additions & 0 deletions test/utils/coder/test_costeer_path_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""Regression test for the pre-refactor CoSTEER import-path shim (see #1331).

The ``demo_traces`` archive linked from the README was pickled before
``rdagent.components.coder.factor_coder.CoSTEER`` was relocated to
``rdagent.components.coder.CoSTEER``. Loading those pickles in
``rdagent ui`` would otherwise fail with ``ModuleNotFoundError`` for the
old module path. The shim installs a meta-path finder that redirects the
old path to the new package.
"""

import importlib
import pickle
import sys
import unittest


class TestCoSTEERPathCompat(unittest.TestCase):
OLD = "rdagent.components.coder.factor_coder.CoSTEER"
NEW = "rdagent.components.coder.CoSTEER"

def setUp(self) -> None:
# Importing the package triggers install_compat_redirector().
import rdagent.components.coder.factor_coder # noqa: F401

def test_old_package_path_resolves_to_new_package(self) -> None:
old = importlib.import_module(self.OLD)
new = importlib.import_module(self.NEW)
self.assertIs(old, new)

def test_old_submodule_paths_resolve_to_new_submodules(self) -> None:
for sub in ["evaluators", "evolving_strategy", "knowledge_management", "task", "config"]:
with self.subTest(submodule=sub):
old = importlib.import_module(f"{self.OLD}.{sub}")
new = importlib.import_module(f"{self.NEW}.{sub}")
self.assertIs(old, new)

def test_pickle_referencing_old_module_path_round_trips(self) -> None:
# Locate a real class from the new module path…
new_evaluators = importlib.import_module(f"{self.NEW}.evaluators")
# Pick any concrete class exposed at module scope.
target_cls = next(
(
obj
for name, obj in vars(new_evaluators).items()
if isinstance(obj, type) and obj.__module__ == new_evaluators.__name__
),
None,
)
self.assertIsNotNone(
target_cls,
"Expected at least one concrete class on the CoSTEER evaluators module",
)

# …re-stamp it as if it had been defined under the *old* import path
# (this is what a pre-refactor pickle records in its serialised form).
original_module = target_cls.__module__
try:
target_cls.__module__ = f"{self.OLD}.evaluators"

# Drop any cached old-path module entries so we exercise the
# full meta-path lookup, the same way pickle.load() does on a
# fresh process.
for key in [k for k in sys.modules if k == self.OLD or k.startswith(self.OLD + ".")]:
del sys.modules[key]

data = pickle.dumps(target_cls)
loaded = pickle.loads(data)
self.assertIs(loaded, target_cls)
finally:
target_cls.__module__ = original_module


if __name__ == "__main__":
unittest.main()
Loading