Skip to content

Commit 576c3ce

Browse files
committed
Restore 1.1 behavior for backwards compatibility
1 parent c348239 commit 576c3ce

File tree

12 files changed

+170
-1
lines changed

12 files changed

+170
-1
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,34 @@ defaults:
221221
222222
Hydra breaks with importlib-resources 6.2+ due to `OrphanPath` objects not having `is_file()`/`is_dir()` methods. Lerna handles this gracefully.
223223

224+
### Plugin Registration Compatible with Hydra
225+
226+
Lerna provides a bridge that allows plugins registered via lerna to work with hydra-core. This enables you to write plugins once and have them work with both frameworks.
227+
228+
#### Registering Plugins via Entry Points
229+
230+
Add your plugin to `pyproject.toml` using the `hydra.lernaplugins` entry point group:
231+
232+
```toml
233+
# For SearchPathPlugin modules:
234+
[project.entry-points."hydra.lernaplugins"]
235+
my-plugin = "my_package.plugin_module"
236+
237+
# For package-style config directories:
238+
[project.entry-points."hydra.lernaplugins"]
239+
my-plugin = "pkg:my_package.hydra"
240+
```
241+
242+
**Module-style entry points** (like `my_package.plugin_module`) are imported and scanned for `SearchPathPlugin` subclasses.
243+
244+
**Package-style entry points** (like `pkg:my_package.hydra`) register config search paths directly.
245+
246+
#### How It Works
247+
248+
When hydra-core is used, lerna's `LernaGenericSearchPathPlugin` (installed in the `hydra_plugins` namespace) discovers all plugins registered under `hydra.lernaplugins` and makes them available to hydra's plugin system.
249+
250+
This enables gradual migration: you can write plugins for lerna and they'll automatically work with existing hydra-core installations.
251+
224252
### Third-Party Plugins
225253

226254
Hydra's plugin ecosystem (Optuna, Ray, Submitit, etc.) references `hydra` internally. To use them with Lerna:

hydra_plugins/lerna/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2+
from .searchpath import LernaGenericSearchPathPlugin # noqa: F401

hydra_plugins/lerna/searchpath.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2+
"""
3+
Bridge plugin that enables hydra-core to discover plugins registered via lerna.
4+
5+
When using hydra-core (not lerna directly), this plugin discovers plugins
6+
registered under the `hydra.lernaplugins` entry point group and makes them
7+
available to hydra's plugin system.
8+
9+
This enables gradual migration: plugins can be written for lerna and still
10+
work with hydra-core installations.
11+
"""
12+
13+
import sys
14+
from importlib import import_module
15+
from logging import getLogger
16+
17+
from hydra.core.config_search_path import ConfigSearchPath
18+
from hydra.core.config_store import ConfigStore
19+
from hydra.plugins.search_path_plugin import SearchPathPlugin
20+
21+
if sys.version_info < (3, 10):
22+
from importlib_metadata import entry_points
23+
else:
24+
from importlib.metadata import entry_points
25+
26+
_log = getLogger("lerna")
27+
28+
# NOTE: use `lernaplugins` instead of `plugins`
29+
# for https://github.com/facebookresearch/hydra/pull/3052
30+
_discovered_plugins = entry_points(group="hydra.lernaplugins")
31+
_searchpaths_pkg = {}
32+
for entry_point in _discovered_plugins:
33+
if entry_point.value.startswith(("pkg:", "file:")):
34+
# This is a package style entry point
35+
kind, pkg_name = entry_point.value.split(":", 1)
36+
_searchpaths_pkg[entry_point.name] = f"{kind}://{pkg_name}"
37+
continue
38+
# Otherwise, it's a module style entry point
39+
try:
40+
mod = import_module(entry_point.value)
41+
except ImportError as e:
42+
_log.warning(f"Failed to import entry point {entry_point.name} from {entry_point.value}: {e}")
43+
continue
44+
for attr in dir(mod):
45+
thing = getattr(mod, attr)
46+
if isinstance(thing, type) and issubclass(thing, SearchPathPlugin):
47+
_log.info(f"Discovered search path plugin: {thing.__name__}")
48+
globals()[thing.__name__] = thing
49+
# search_path.append(provider=entry_point.name, path=entry_point.value)
50+
51+
52+
class LernaGenericSearchPathPlugin(SearchPathPlugin):
53+
"""
54+
A SearchPathPlugin that bridges lerna plugins to hydra-core.
55+
56+
This plugin is automatically discovered by hydra-core due to being in
57+
the hydra_plugins namespace. It then discovers any plugins registered
58+
under the `hydra.lernaplugins` entry point group.
59+
"""
60+
61+
def manipulate_search_path(self, search_path: ConfigSearchPath) -> None:
62+
if _searchpaths_pkg:
63+
for provider, path in _searchpaths_pkg.items():
64+
inst = ConfigStore.instance()
65+
inst.store(name=provider, node=None, group=provider, provider=provider)
66+
search_path.append(provider=provider, path=path)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved

lerna/tests/fake_package/fake_package/hydra/.gitkeep

Whitespace-only changes.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
2+
from hydra.core.config_search_path import ConfigSearchPath
3+
from hydra.core.config_store import ConfigStore
4+
from hydra.plugins.search_path_plugin import SearchPathPlugin
5+
6+
7+
class FakePackageSearchPathPlugin(SearchPathPlugin):
8+
def manipulate_search_path(self, search_path: ConfigSearchPath) -> None:
9+
inst = ConfigStore.instance()
10+
inst.store(name="fake-package", node=None, group="fake_package", provider="fake-package")
11+
search_path.append(provider="fake-package", path="pkg://fake_package/hydra")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[project]
2+
name = "fake-package"
3+
version = "0.0.0"
4+
requires-python = ">=3.9"
5+
dependencies = []
6+
7+
[project.entry-points."hydra.lernaplugins"]
8+
fake-package = "fake_package.plugin"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved

lerna/tests/fake_package2/fake_package2/hydra/.gitkeep

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[project]
2+
name = "fake-package2"
3+
version = "0.0.0"
4+
requires-python = ">=3.9"
5+
dependencies = []
6+
7+
[project.entry-points."hydra.lernaplugins"]
8+
fake-package2 = "pkg:fake_package2.hydra"

0 commit comments

Comments
 (0)