Skip to content

Commit eb51468

Browse files
add entrypoint hooks and function to add config sources
1 parent 2646eb5 commit eb51468

File tree

3 files changed

+68
-2
lines changed

3 files changed

+68
-2
lines changed

src/noob/config.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
from importlib.metadata import entry_points
13
from pathlib import Path
24
from typing import Literal
35

@@ -11,9 +13,15 @@
1113
YamlConfigSettingsSource,
1214
)
1315

16+
from noob.exceptions import EntrypointImportWarning
17+
1418
_default_userdir = Path().home() / ".config" / "noob"
1519
_dirs = PlatformDirs("noob", "noob")
1620
LOG_LEVELS = Literal["DEBUG", "INFO", "WARNING", "ERROR"]
21+
_extra_sources = []
22+
"""Extra sources for tube configs added by `add_sources`"""
23+
_entrypoint_sources: list[Path] | None = None
24+
"""Sources added by entrypoint functions. Initially `None`, populated on first load of a config"""
1725

1826

1927
class LogConfig(BaseModel):
@@ -124,4 +132,54 @@ def settings_customise_sources(
124132
)
125133

126134

135+
def add_config_source(path: Path) -> None:
136+
"""
137+
Add a directory as a source of tube configs when searching by tube id
138+
"""
139+
global _extra_sources
140+
path = Path(path)
141+
_extra_sources.append(path)
142+
143+
144+
def get_entrypoint_sources() -> list[Path]:
145+
"""
146+
Get additional config sources added by entrypoint functions.
147+
148+
Packages that ship noob tubes can make those tubes available by adding an
149+
entrypoint function with a signature ``() -> list[Path]`` to their pyproject.toml
150+
like:
151+
152+
[project.entry-points."noob.add_sources"]
153+
tubes = "my_package.something:add_sources"
154+
155+
References:
156+
https://setuptools.pypa.io/en/latest/userguide/entry_point.html
157+
"""
158+
global _entrypoint_sources
159+
if _entrypoint_sources is None:
160+
_entrypoint_sources = []
161+
for ext in entry_points(group="noob.add_sources"):
162+
try:
163+
add_sources_fn = ext.load()
164+
except (ImportError, AttributeError):
165+
warnings.warn(
166+
f"Config source entrypoint {ext.name}, {ext.value} "
167+
f"could not be imported, or the function could not be found. Ignoring",
168+
EntrypointImportWarning,
169+
stacklevel=1,
170+
)
171+
continue
172+
try:
173+
_entrypoint_sources.extend([Path(p) for p in add_sources_fn()])
174+
except Exception as e:
175+
# bare exception is fine here - we're calling external code and can't know.
176+
warnings.warn(
177+
f"Config source entrypoint {ext.name}, {ext.value} "
178+
f"threw an error, or returned an invalid list of paths, ignoring.\n{str(e)}",
179+
EntrypointImportWarning,
180+
stacklevel=1,
181+
)
182+
return _entrypoint_sources
183+
184+
127185
config = Config()

src/noob/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class ConfigError(NoobError):
1616
"""Base config error type"""
1717

1818

19+
class ConfigWarning(NoobWarning):
20+
"""Base config warning type"""
21+
22+
1923
class SchedulerError(NoobError):
2024
"""Base error in the scheduler"""
2125

@@ -51,6 +55,10 @@ class ConfigMismatchError(ConfigError, ValueError):
5155
"""
5256

5357

58+
class EntrypointImportWarning(ConfigWarning, ImportWarning):
59+
"""Some problem with a configuration entypoint, usually when importing"""
60+
61+
5462
class AlreadyRunningError(RunnerError, RuntimeError):
5563
"""
5664
A tube is already running!

src/noob/yaml.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,9 @@ def config_sources(cls: type[Self]) -> list[Path]:
199199
Directories to search for config files, in order of priority
200200
such that earlier sources are preferred over later sources.
201201
"""
202-
from noob.config import Config
202+
from noob.config import Config, _extra_sources, get_entrypoint_sources
203203

204-
return [Config().config_dir]
204+
return [Config().config_dir, *_extra_sources, *get_entrypoint_sources()]
205205

206206
def _dump_data(self, **kwargs: Any) -> dict:
207207
"""Ensure that header is prepended to model data"""

0 commit comments

Comments
 (0)