Checked configuration values for their types. Fixes #1150.

This commit is contained in:
Robert Lehmann 2014-10-30 15:46:02 +01:00
parent 2710f705a1
commit 5cc72ca52b
4 changed files with 44 additions and 4 deletions

View File

@ -124,6 +124,7 @@ class Sphinx(object):
self.config = Config(confdir, CONFIG_FILENAME,
confoverrides or {}, self.tags)
self.config.check_unicode(self.warn)
# defer checking types until i18n has been initialized
# set confdir to srcdir if -C given (!= no confdir); a few pieces
# of code expect a confdir to be set
@ -172,6 +173,8 @@ class Sphinx(object):
# set up translation infrastructure
self._init_i18n()
# check all configuration values for permissible types
self.config.check_types(self.warn)
# set up the build environment
self._init_env(freshenv)
# set up the builder

View File

@ -14,7 +14,7 @@ from os import path
from six import PY3, iteritems, string_types, binary_type, integer_types
from sphinx.errors import ConfigError
from sphinx.errors import ConfigError, ConfigWarning
from sphinx.locale import l_
from sphinx.util.osutil import make_filename, cd
from sphinx.util.pycompat import execfile_
@ -249,6 +249,30 @@ class Config(object):
self.setup = config.get('setup', None)
self.extensions = config.get('extensions', [])
def check_types(self, warn):
# check all values for deviation from the default value's type, since
# that can result in TypeErrors all over the place
# NB. since config values might use l_() we have to wait with calling
# this method until i18n is initialized
for name in self._raw_config:
if name not in Config.config_values:
continue # we don't know a default value
default, dummy_rebuild = Config.config_values[name]
if hasattr(default, '__call__'):
default = default(self) # could invoke l_()
if default is None:
continue
current = self[name]
if type(current) is type(default):
continue
common_bases = (
set(type(current).__bases__) & set(type(default).__bases__))
common_bases.discard(object)
if common_bases:
continue # at least we share a non-trivial base class
warn("the config value %r has type `%s', defaults to `%s.'"
% (name, type(current).__name__, type(default).__name__))
def check_unicode(self, warn):
# check all string values for non-ASCII characters in bytestrings,
# since that can result in UnicodeErrors all over the place
@ -296,7 +320,6 @@ class Config(object):
for name in config:
if name in self.values:
self.__dict__[name] = config[name]
del self._raw_config
def __getattr__(self, name):
if name.startswith('_'):

View File

@ -3,8 +3,8 @@
sphinx.errors
~~~~~~~~~~~~~
Contains SphinxError and a few subclasses (in an extra module to avoid
circular import problems).
Contains SphinxError, a few subclasses (in an extra module to avoid
circular import problems), and related classes.
:copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
@ -75,3 +75,9 @@ class SphinxParallelError(Exception):
def __str__(self):
return traceback.format_exception_only(
self.orig_exc.__class__, self.orig_exc)[0].strip()
class ConfigWarning(UserWarning):
"""
Base category for warnings about dubious configuration values.
"""
pass

View File

@ -133,3 +133,11 @@ def test_config_eol(tmpdir):
cfg = Config(tmpdir, 'conf.py', {}, None)
cfg.init_values(lambda warning: 1/0)
assert cfg.project == u'spam'
@with_app(confoverrides={'master_doc': 123})
def test_check_types(app, status, warning):
# WARNING: the config value 'master_doc' has type `int', defaults to `str.'
assert any(buf.startswith('WARNING:')
and 'master_doc' in buf and 'int' in buf and 'str' in buf
for buf in warning.buflist)