From 4c4421b28ff0dc9a564f8a1d072abc015b003b17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torbj=C3=B8rn=20Lindahl?= Date: Wed, 1 Apr 2026 12:07:53 +0200 Subject: [PATCH] fix(openrouter): clean up registry loading --- providers/registries/openrouter.py | 29 +++++++-------- tests/test_openrouter_registry.py | 59 +++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/providers/registries/openrouter.py b/providers/registries/openrouter.py index af908e2..b9b6acb 100644 --- a/providers/registries/openrouter.py +++ b/providers/registries/openrouter.py @@ -22,18 +22,10 @@ class OpenRouterModelRegistry(CapabilityModelRegistry): LIVE_DEFAULT_FILENAME = "openrouter_models_live.json" def __init__(self, config_path: str | None = None, live_config_path: str | None = None) -> None: - self._live_use_resources = False + self._live_resource = None self._live_config_path: Path | None = None self._live_default_path = Path(__file__).resolve().parents[3] / "conf" / self.LIVE_DEFAULT_FILENAME - super().__init__( - env_var_name="OPENROUTER_MODELS_CONFIG_PATH", - default_filename="openrouter_models.json", - provider=ProviderType.OPENROUTER, - friendly_prefix="OpenRouter ({model})", - config_path=config_path, - ) - if live_config_path: self._live_config_path = Path(live_config_path) else: @@ -44,13 +36,19 @@ class OpenRouterModelRegistry(CapabilityModelRegistry): try: resource = importlib.resources.files("conf").joinpath(self.LIVE_DEFAULT_FILENAME) if hasattr(resource, "read_text"): - self._live_use_resources = True + self._live_resource = resource else: raise AttributeError("resource accessor not available") except Exception: self._live_config_path = self._live_default_path - self.reload() + super().__init__( + env_var_name="OPENROUTER_MODELS_CONFIG_PATH", + default_filename="openrouter_models.json", + provider=ProviderType.OPENROUTER, + friendly_prefix="OpenRouter ({model})", + config_path=config_path, + ) def reload(self) -> None: live_data = self._load_live_config_data() @@ -62,13 +60,12 @@ class OpenRouterModelRegistry(CapabilityModelRegistry): self._build_maps(configs) def _load_live_config_data(self) -> dict: - if self._live_use_resources: + if self._live_resource is not None: try: - resource = importlib.resources.files("conf").joinpath(self.LIVE_DEFAULT_FILENAME) - if hasattr(resource, "read_text"): - config_text = resource.read_text(encoding="utf-8") + if hasattr(self._live_resource, "read_text"): + config_text = self._live_resource.read_text(encoding="utf-8") else: - with resource.open("r", encoding="utf-8") as handle: + with self._live_resource.open("r", encoding="utf-8") as handle: config_text = handle.read() data = json.loads(config_text) except FileNotFoundError: diff --git a/tests/test_openrouter_registry.py b/tests/test_openrouter_registry.py index 437261f..dc02f79 100644 --- a/tests/test_openrouter_registry.py +++ b/tests/test_openrouter_registry.py @@ -23,10 +23,14 @@ class TestOpenRouterModelRegistry: assert len(registry.list_models()) > 0 assert len(registry.list_aliases()) > 0 - def test_registry_initialization_with_live_catalogue_defaults(self): + def test_default_init_resolves_live_only_model(self): registry = OpenRouterModelRegistry() - assert registry.list_models() + config = registry.resolve("x-ai/grok-4") + assert config is not None + assert config.model_name == "x-ai/grok-4" + assert config.context_window == 256000 + assert config.supports_extended_thinking is True def test_custom_config_path(self): """Test registry with custom config path.""" @@ -48,17 +52,15 @@ class TestOpenRouterModelRegistry: try: registry = OpenRouterModelRegistry(config_path=temp_path) - assert len(registry.list_models()) == 1 assert "test/model-1" in registry.list_models() assert "test1" in registry.list_aliases() assert "t1" in registry.list_aliases() + assert registry.resolve("x-ai/grok-4") is not None finally: os.unlink(temp_path) - def test_environment_variable_override(self): + def test_environment_variable_override(self, monkeypatch): """Test OPENROUTER_MODELS_CONFIG_PATH environment variable.""" - original_env = os.environ.get("OPENROUTER_MODELS_CONFIG_PATH") - # Create custom config config_data = { "models": [ @@ -72,7 +74,7 @@ class TestOpenRouterModelRegistry: try: # Set environment variable - os.environ["OPENROUTER_MODELS_CONFIG_PATH"] = temp_path + monkeypatch.setenv("OPENROUTER_MODELS_CONFIG_PATH", temp_path) # Create registry without explicit path registry = OpenRouterModelRegistry() @@ -82,11 +84,6 @@ class TestOpenRouterModelRegistry: assert "envtest" in registry.list_aliases() finally: - # Restore environment - if original_env is not None: - os.environ["OPENROUTER_MODELS_CONFIG_PATH"] = original_env - else: - del os.environ["OPENROUTER_MODELS_CONFIG_PATH"] os.unlink(temp_path) def test_alias_resolution(self): @@ -202,9 +199,8 @@ class TestOpenRouterModelRegistry: with patch.dict("os.environ", {}, clear=True): registry = OpenRouterModelRegistry(config_path="/non/existent/path.json") - # Should initialize with empty maps - assert len(registry.list_models()) == 0 - assert len(registry.list_aliases()) == 0 + assert len(registry.list_models()) > 0 + assert registry.resolve("x-ai/grok-4") is not None assert registry.resolve("anything") is None def test_invalid_json_config(self): @@ -215,9 +211,8 @@ class TestOpenRouterModelRegistry: try: registry = OpenRouterModelRegistry(config_path=temp_path) - # Should handle gracefully and initialize empty - assert len(registry.list_models()) == 0 - assert len(registry.list_aliases()) == 0 + assert len(registry.list_models()) > 0 + assert registry.resolve("x-ai/grok-4") is not None finally: os.unlink(temp_path) @@ -348,6 +343,34 @@ class TestOpenRouterModelRegistry: assert "openai/o3" in registry.list_models() assert registry.resolve("o3") is not None + def test_invalid_live_json_keeps_curated_models_working(self): + curated_data = { + "models": [ + { + "model_name": "openai/gpt-5.2", + "aliases": ["gpt5.2"], + "context_window": 400000, + "max_output_tokens": 128000, + } + ] + } + + with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as curated_file: + json.dump(curated_data, curated_file) + curated_path = curated_file.name + + with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as live_file: + live_file.write("{ invalid json }") + live_path = live_file.name + + try: + registry = OpenRouterModelRegistry(config_path=curated_path, live_config_path=live_path) + assert "openai/gpt-5.2" in registry.list_models() + assert registry.resolve("gpt5.2") is not None + finally: + os.unlink(curated_path) + os.unlink(live_path) + def test_model_with_all_capabilities(self): """Test model with all capability flags.""" from providers.shared import TemperatureConstraint