32
32
from importlib_metadata import entry_points
33
33
34
34
if TYPE_CHECKING :
35
+ from collections .abc import Callable
35
36
from typing import TypedDict
36
37
37
38
from typing_extensions import Required
@@ -156,6 +157,7 @@ class HTMLThemeFactory:
156
157
def __init__ (self , app : Sphinx ) -> None :
157
158
self ._app = app
158
159
self ._themes = app .registry .html_themes
160
+ self ._entry_point_themes : dict [str , Callable [[], None ]] = {}
159
161
self ._load_builtin_themes ()
160
162
if getattr (app .config , 'html_theme_path' , None ):
161
163
self ._load_additional_themes (app .config .html_theme_path )
@@ -183,8 +185,16 @@ def _load_entry_point_themes(self) -> None:
183
185
for entry_point in entry_points (group = 'sphinx.html_themes' ):
184
186
if entry_point .name in self ._themes :
185
187
continue # don't overwrite loaded themes
186
- self ._app .registry .load_extension (self ._app , entry_point .module )
187
- _config_post_init (self ._app , self ._app .config )
188
+
189
+ def _load_theme_closure (
190
+ # bind variables in the function definition
191
+ app : Sphinx = self ._app ,
192
+ theme_module : str = entry_point .module ,
193
+ ) -> None :
194
+ app .setup_extension (theme_module )
195
+ _config_post_init (app , app .config )
196
+
197
+ self ._entry_point_themes [entry_point .name ] = _load_theme_closure
188
198
189
199
@staticmethod
190
200
def _find_themes (theme_path : str ) -> dict [str , str ]:
@@ -217,10 +227,18 @@ def _find_themes(theme_path: str) -> dict[str, str]:
217
227
218
228
def create (self , name : str ) -> Theme :
219
229
"""Create an instance of theme."""
230
+ if name in self ._entry_point_themes :
231
+ # Load a deferred theme from an entry point
232
+ entry_point_loader = self ._entry_point_themes [name ]
233
+ entry_point_loader ()
220
234
if name not in self ._themes :
221
235
raise ThemeError (__ ('no theme named %r found (missing theme.toml?)' ) % name )
222
236
223
- themes , theme_dirs , tmp_dirs = _load_theme_with_ancestors (self ._themes , name )
237
+ themes , theme_dirs , tmp_dirs = _load_theme_with_ancestors (
238
+ name ,
239
+ self ._themes ,
240
+ self ._entry_point_themes ,
241
+ )
224
242
return Theme (name , configs = themes , paths = theme_dirs , tmp_dirs = tmp_dirs )
225
243
226
244
@@ -235,7 +253,10 @@ def _is_archived_theme(filename: str, /) -> bool:
235
253
236
254
237
255
def _load_theme_with_ancestors (
238
- theme_paths : dict [str , str ], name : str , /
256
+ name : str ,
257
+ theme_paths : dict [str , str ],
258
+ entry_point_themes : dict [str , Callable [[], None ]],
259
+ / ,
239
260
) -> tuple [dict [str , _ConfigFile ], list [str ], list [str ]]:
240
261
themes : dict [str , _ConfigFile ] = {}
241
262
theme_dirs : list [str ] = []
@@ -253,6 +274,10 @@ def _load_theme_with_ancestors(
253
274
if inherit in themes :
254
275
msg = __ ('The %r theme has circular inheritance' ) % name
255
276
raise ThemeError (msg )
277
+ if inherit in entry_point_themes and inherit not in theme_paths :
278
+ # Load a deferred theme from an entry point
279
+ entry_point_loader = entry_point_themes [inherit ]
280
+ entry_point_loader ()
256
281
if inherit not in theme_paths :
257
282
msg = __ (
258
283
'The %r theme inherits from %r, which is not a loaded theme. '
0 commit comments