@@ -252,6 +252,12 @@ def __init__(self, config: dict[str, Any] | None = None,
252252 self ._overrides = dict (overrides ) if overrides is not None else {}
253253 self ._options = Config .config_values .copy ()
254254 self ._raw_config = raw_config
255+
256+ for name in list (self ._overrides .keys ()):
257+ if '.' in name :
258+ real_name , key = name .split ('.' , 1 )
259+ raw_config .setdefault (real_name , {})[key ] = self ._overrides .pop (name )
260+
255261 self .setup : _ExtensionSetupFunc | None = raw_config .get ('setup' )
256262
257263 if 'extensions' in self ._overrides :
@@ -292,90 +298,60 @@ def read(cls, confdir: str | os.PathLike[str], overrides: dict | None = None,
292298
293299 return cls (namespace , overrides )
294300
295- def convert_overrides (self , name : str , value : Any ) -> Any :
296- if not isinstance (value , str ):
301+ def convert_overrides (self , name : str , value : str ) -> Any :
302+ opt = self ._options [name ]
303+ default = opt .default
304+ valid_types = opt .valid_types
305+ if valid_types == Any :
297306 return value
298- else :
299- opt = self ._options [name ]
300- default = opt .default
301- valid_types = opt .valid_types
302- if valid_types == Any :
303- return value
304- elif (type (default ) is bool
305- or (not isinstance (valid_types , ENUM )
306- and len (valid_types ) == 1 and bool in valid_types )):
307- if isinstance (valid_types , ENUM ) or len (valid_types ) > 1 :
308- # if valid_types are given, and non-bool valid types exist,
309- # return the value without coercing to a Boolean.
310- return value
311- # given falsy string from a command line option
312- return value not in {'0' , '' }
313- elif isinstance (default , dict ):
314- raise ValueError (__ ('cannot override dictionary config setting %r, '
315- 'ignoring (use %r to set individual elements)' ) %
316- (name , name + '.key=value' ))
317- elif isinstance (default , list ):
318- return value .split (',' )
319- elif isinstance (default , int ):
320- try :
321- return int (value )
322- except ValueError as exc :
323- raise ValueError (__ ('invalid number %r for config value %r, ignoring' ) %
324- (value , name )) from exc
325- elif callable (default ):
326- return value
327- elif default is not None and not isinstance (default , str ):
328- raise ValueError (__ ('cannot override config setting %r with unsupported '
329- 'type, ignoring' ) % name )
330- else :
307+ elif (type (default ) is bool
308+ or (not isinstance (valid_types , ENUM )
309+ and len (valid_types ) == 1 and bool in valid_types )):
310+ if isinstance (valid_types , ENUM ) or len (valid_types ) > 1 :
311+ # if valid_types are given, and non-bool valid types exist,
312+ # return the value without coercing to a Boolean.
331313 return value
332-
333- def pre_init_values (self ) -> None :
334- """
335- Initialize some limited config variables before initializing i18n and loading
336- extensions.
337- """
338- for name in 'needs_sphinx' , 'suppress_warnings' , 'language' , 'locale_dirs' :
314+ # given falsy string from a command line option
315+ return value not in {'0' , '' }
316+ elif isinstance (default , dict ):
317+ raise ValueError (__ ('cannot override dictionary config setting %r, '
318+ 'ignoring (use %r to set individual elements)' ) %
319+ (name , f'{ name } .key=value' ))
320+ elif isinstance (default , list ):
321+ return value .split (',' )
322+ elif isinstance (default , int ):
339323 try :
340- if name in self ._overrides :
341- self .__dict__ [name ] = self .convert_overrides (name , self ._overrides [name ])
342- elif name in self ._raw_config :
343- self .__dict__ [name ] = self ._raw_config [name ]
324+ return int (value )
344325 except ValueError as exc :
345- logger .warning ("%s" , exc )
326+ raise ValueError (__ ('invalid number %r for config value %r, ignoring' ) %
327+ (value , name )) from exc
328+ elif callable (default ):
329+ return value
330+ elif default is not None and not isinstance (default , str ):
331+ raise ValueError (__ ('cannot override config setting %r with unsupported '
332+ 'type, ignoring' ) % name )
333+ else :
334+ return value
346335
347- def init_values (self ) -> None :
348- config = self ._raw_config
349- for name , value in self ._overrides .items ():
350- try :
351- if '.' in name :
352- real_name , key = name .split ('.' , 1 )
353- config .setdefault (real_name , {})[key ] = value
354- continue
355- if name not in self ._options :
356- logger .warning (__ ('unknown config value %r in override, ignoring' ),
357- name )
358- continue
359- if isinstance (value , str ):
360- config [name ] = self .convert_overrides (name , value )
361- else :
362- config [name ] = value
363- except ValueError as exc :
364- logger .warning ("%s" , exc )
365- for name in config :
366- if name in self ._options :
367- self .__dict__ [name ] = config [name ]
336+ @staticmethod
337+ def pre_init_values () -> None :
338+ # method only retained for compatability
339+ pass
340+ # warnings.warn(
341+ # 'Config.pre_init_values() will be removed in Sphinx 9.0 or later',
342+ # RemovedInSphinx90Warning, stacklevel=2)
368343
369- def post_init_values (self ) -> None :
370- """
371- Initialize additional config variables that are added after init_values() called.
372- """
373- config = self ._raw_config
374- for name in config :
375- if name not in self .__dict__ and name in self ._options :
376- self .__dict__ [name ] = config [name ]
344+ def init_values (self ) -> None :
345+ # method only retained for compatability
346+ self ._report_override_warnings ()
347+ # warnings.warn(
348+ # 'Config.init_values() will be removed in Sphinx 9.0 or later',
349+ # RemovedInSphinx90Warning, stacklevel=2)
377350
378- check_confval_types (None , self )
351+ def _report_override_warnings (self ) -> None :
352+ for name in self ._overrides :
353+ if name not in self ._options :
354+ logger .warning (__ ('unknown config value %r in override, ignoring' ), name )
379355
380356 def __repr__ (self ):
381357 values = []
@@ -388,14 +364,35 @@ def __repr__(self):
388364 return self .__class__ .__qualname__ + '(' + ', ' .join (values ) + ')'
389365
390366 def __getattr__ (self , name : str ) -> Any :
367+ if name in self ._options :
368+ # first check command-line overrides
369+ if name in self ._overrides :
370+ value = self ._overrides [name ]
371+ if not isinstance (value , str ):
372+ self .__dict__ [name ] = value
373+ return value
374+ try :
375+ value = self .convert_overrides (name , value )
376+ except ValueError as exc :
377+ logger .warning ("%s" , exc )
378+ else :
379+ self .__dict__ [name ] = value
380+ return value
381+ # then check values from 'conf.py'
382+ if name in self ._raw_config :
383+ self .__dict__ [name ] = value = self ._raw_config [name ]
384+ return value
385+ # finally, fall back to the default value
386+ default = self ._options [name ].default
387+ if callable (default ):
388+ return default (self )
389+ self .__dict__ [name ] = default
390+ return default
391391 if name .startswith ('_' ):
392- raise AttributeError (name )
393- if name not in self ._options :
394- raise AttributeError (__ ('No such config value: %s' ) % name )
395- default = self ._options [name ].default
396- if callable (default ):
397- return default (self )
398- return default
392+ msg = f'{ self .__class__ .__name__ !r} object has no attribute { name !r} '
393+ raise AttributeError (msg )
394+ msg = __ ('No such config value: %r' ) % name
395+ raise AttributeError (msg )
399396
400397 def __getitem__ (self , name : str ) -> Any :
401398 return getattr (self , name )
@@ -452,10 +449,12 @@ def __getstate__(self) -> dict:
452449 return __dict__
453450
454451 def __setstate__ (self , state : dict ) -> None :
452+ self ._overrides = {}
455453 self ._options = {
456454 name : _Opt (real_value , rebuild , ())
457455 for name , (real_value , rebuild ) in state .pop ('_options' ).items ()
458456 }
457+ self ._raw_config = {}
459458 self .__dict__ .update (state )
460459
461460
0 commit comments