|
| 1 | +import warnings |
| 2 | +from importlib.metadata import entry_points |
1 | 3 | from pathlib import Path |
2 | 4 | from typing import Literal |
3 | 5 |
|
|
11 | 13 | YamlConfigSettingsSource, |
12 | 14 | ) |
13 | 15 |
|
| 16 | +from noob.exceptions import EntrypointImportWarning |
| 17 | + |
14 | 18 | _default_userdir = Path().home() / ".config" / "noob" |
15 | 19 | _dirs = PlatformDirs("noob", "noob") |
16 | 20 | 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""" |
17 | 25 |
|
18 | 26 |
|
19 | 27 | class LogConfig(BaseModel): |
@@ -124,4 +132,54 @@ def settings_customise_sources( |
124 | 132 | ) |
125 | 133 |
|
126 | 134 |
|
| 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 | + |
127 | 185 | config = Config() |
0 commit comments