Refactor: Add Config.read() as a constructor

To simplify Config.__init__() method, this separates conf.py parsing
feature to Config.read() method.
This allows to instantiate config object simply.
This commit is contained in:
Takeshi KOMIYA
2018-03-23 01:04:56 +09:00
parent f26db5b228
commit ba83214386
6 changed files with 75 additions and 34 deletions

View File

@@ -32,6 +32,8 @@ Deprecated
deprecated
* ``sphinx.locale.l_()`` is deprecated
* #2157: helper function ``warn()`` for HTML themes is deprecated
* ``Config.__init__()`` has changed; the *dirname*, *filename* and *tags*
argument has been deprecated
For more details, see `deprecation APIs list
<http://www.sphinx-doc.org/en/master/extdev/index.html#deprecated-apis>`_
@@ -51,6 +53,8 @@ Features added
* helper function ``warning()`` for HTML themes is added
* Add ``Domain.enumerable_nodes`` to manage own enumerable nodes for domains
(experimental)
* Add ``Config.read()`` classmethod to create a new config object from
configuration file
Bugs fixed
----------

View File

@@ -113,6 +113,12 @@ The following is a list of deprecated interface.
- (will be) Removed
- Alternatives
* - ``dirname``, ``filename`` and ``tags`` arguments of
``Config.__init__()``
- 1.8
- 3.0
- ``Config.read()``
* - ``warn()`` (template helper function)
- 1.8
- 3.0

View File

@@ -187,8 +187,8 @@ class Sphinx(object):
# read config
self.tags = Tags(tags)
self.config = Config(self.confdir, CONFIG_FILENAME,
confoverrides or {}, self.tags)
self.config = Config.read(self.confdir, CONFIG_FILENAME,
confoverrides or {}, self.tags)
self.config.check_unicode()
# defer checking types until i18n has been initialized

View File

@@ -11,12 +11,14 @@
import re
import traceback
import warnings
from collections import OrderedDict
from os import path, getenv
from typing import Any, NamedTuple, Union
from six import PY2, PY3, iteritems, string_types, binary_type, text_type, integer_types
from sphinx.deprecation import RemovedInSphinx30Warning
from sphinx.errors import ConfigError
from sphinx.locale import _, __
from sphinx.util import logging
@@ -35,12 +37,6 @@ logger = logging.getLogger(__name__)
nonascii_re = re.compile(br'[\x80-\xff]')
copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])')
CONFIG_SYNTAX_ERROR = __("There is a syntax error in your configuration file: %s")
if PY3:
CONFIG_SYNTAX_ERROR += __("\nDid you change the syntax from 2.x to 3.x?")
CONFIG_ERROR = __("There is a programable error in your configuration file:\n\n%s")
CONFIG_EXIT_ERROR = __("The configuration file (or one of the modules it imports) "
"called sys.exit()")
CONFIG_ENUM_WARNING = __("The config value `{name}` has to be a one of {candidates}, "
"but `{current}` is given.")
CONFIG_PERMITTED_TYPE_WARNING = __("The config value `{name}' has type `{current.__name__}', "
@@ -159,30 +155,27 @@ class Config(object):
'env'),
) # type: Dict[unicode, Tuple]
def __init__(self, dirname, filename, overrides, tags):
# type: (unicode, unicode, Dict, Tags) -> None
def __init__(self, *args):
# type: (Any) -> None
if len(args) == 4:
# old style arguments: (dirname, filename, overrides, tags)
warnings.warn('The argument of Config() class has been changed. '
'Use Config.read() to read configuration from conf.py.',
RemovedInSphinx30Warning)
dirname, filename, overrides, tags = args
config = eval_config_file(dirname, filename, tags)
else:
# new style arguments: (config={}, overrides={})
if len(args) == 0:
config, overrides = {}, {}
elif len(args) == 1:
config, overrides = args[0], {}
else:
config, overrides = args[:2]
self.overrides = overrides
self.values = Config.config_values.copy()
config = {} # type: Dict[unicode, Any]
if dirname is not None:
config_file = path.join(dirname, filename)
config['__file__'] = config_file
config['tags'] = tags
with cd(dirname):
# we promise to have the config dir as current dir while the
# config file is executed
try:
execfile_(filename, config)
except SyntaxError as err:
raise ConfigError(CONFIG_SYNTAX_ERROR % err)
except SystemExit:
raise ConfigError(CONFIG_EXIT_ERROR)
except Exception:
raise ConfigError(CONFIG_ERROR % traceback.format_exc())
self._raw_config = config
# these two must be preinitialized because extensions can add their
# own config values
self.setup = config.get('setup', None) # type: Callable
if 'extensions' in overrides:
@@ -201,6 +194,13 @@ class Config(object):
config[k] = copyright_year_re.sub(r'\g<1>%s' % format_date('%Y'),
config[k])
@classmethod
def read(cls, confdir, filename, overrides=None, tags=None):
# type: (unicode, unicode, Dict, Tags) -> Config
"""Create a Config object from configuration file."""
namespace = eval_config_file(confdir, filename, tags)
return Config(namespace, overrides or {})
def check_types(self):
# type: () -> None
# check all values for deviation from the default value's type, since
@@ -365,6 +365,37 @@ class Config(object):
return (value for value in self if value.rebuild in rebuild) # type: ignore
def eval_config_file(confdir, filename, tags):
# type: (unicode, unicode, Tags) -> Dict[unicode, Any]
"""Evaluate a config file."""
if confdir is None:
return {}
config_path = path.join(confdir, filename)
namespace = {} # type: Dict[unicode, Any]
namespace['__file__'] = config_path
namespace['tags'] = tags
with cd(confdir):
# during executing config file, current dir is changed to ``confdir``.
try:
execfile_(filename, namespace)
except SyntaxError as err:
msg = __("There is a syntax error in your configuration file: %s")
if PY3:
msg += __("\nDid you change the syntax from 2.x to 3.x?")
raise ConfigError(msg % err)
except SystemExit:
msg = __("The configuration file (or one of the modules it imports) "
"called sys.exit()")
raise ConfigError(msg)
except Exception:
msg = __("There is a programable error in your configuration file:\n\n%s")
raise ConfigError(msg % traceback.format_exc())
return namespace
def convert_source_suffix(app, config):
# type: (Sphinx, Config) -> None
"""This converts old styled source_suffix to new styled one.

View File

@@ -96,14 +96,14 @@ def test_errors_warnings(logger, tempdir):
# test the error for syntax errors in the config file
(tempdir / 'conf.py').write_text(u'project = \n', encoding='ascii')
with pytest.raises(ConfigError) as excinfo:
Config(tempdir, 'conf.py', {}, None)
Config.read(tempdir, 'conf.py', {}, None)
assert 'conf.py' in str(excinfo.value)
# test the automatic conversion of 2.x only code in configs
(tempdir / 'conf.py').write_text(
u'# -*- coding: utf-8\n\nproject = u"Jägermeister"\n',
encoding='utf-8')
cfg = Config(tempdir, 'conf.py', {}, None)
cfg = Config.read(tempdir, 'conf.py', {}, None)
cfg.init_values()
assert cfg.project == u'Jägermeister'
assert logger.called is False
@@ -115,7 +115,7 @@ def test_errors_warnings(logger, tempdir):
return
(tempdir / 'conf.py').write_text(
u'# -*- coding: latin-1\nproject = "fooä"\n', encoding='latin-1')
cfg = Config(tempdir, 'conf.py', {}, None)
cfg = Config.read(tempdir, 'conf.py', {}, None)
assert logger.warning.called is False
cfg.check_unicode()
@@ -174,7 +174,7 @@ def test_config_eol(logger, tempdir):
configfile = tempdir / 'conf.py'
for eol in (b'\n', b'\r\n'):
configfile.write_bytes(b'project = "spam"' + eol)
cfg = Config(tempdir, 'conf.py', {}, None)
cfg = Config.read(tempdir, 'conf.py', {}, None)
cfg.init_values()
assert cfg.project == u'spam'
assert logger.called is False

View File

@@ -17,7 +17,7 @@ from sphinx.config import Config
from sphinx.directives.code import LiteralIncludeReader
from sphinx.testing.util import etree_parse
DUMMY_CONFIG = Config(None, None, {}, '')
DUMMY_CONFIG = Config({}, {})
@pytest.fixture(scope='module')