mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merged in lehmannro/sphinx-warnconfig (pull request #314)
Check configuration values for their types
This commit is contained in:
commit
c0a9d7740e
3
CHANGES
3
CHANGES
@ -12,6 +12,9 @@ Features added
|
|||||||
|
|
||||||
* #1597: Added possibility to return a new template name from
|
* #1597: Added possibility to return a new template name from
|
||||||
`html-page-context`.
|
`html-page-context`.
|
||||||
|
* PR#314, #1150: Configuration values are now checked for their type. A
|
||||||
|
warning is raised if the configured and the default value do not have the
|
||||||
|
same type and do not share a common non-trivial base class.
|
||||||
|
|
||||||
Bugs fixed
|
Bugs fixed
|
||||||
----------
|
----------
|
||||||
|
@ -124,6 +124,7 @@ class Sphinx(object):
|
|||||||
self.config = Config(confdir, CONFIG_FILENAME,
|
self.config = Config(confdir, CONFIG_FILENAME,
|
||||||
confoverrides or {}, self.tags)
|
confoverrides or {}, self.tags)
|
||||||
self.config.check_unicode(self.warn)
|
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
|
# set confdir to srcdir if -C given (!= no confdir); a few pieces
|
||||||
# of code expect a confdir to be set
|
# of code expect a confdir to be set
|
||||||
@ -172,6 +173,8 @@ class Sphinx(object):
|
|||||||
|
|
||||||
# set up translation infrastructure
|
# set up translation infrastructure
|
||||||
self._init_i18n()
|
self._init_i18n()
|
||||||
|
# check all configuration values for permissible types
|
||||||
|
self.config.check_types(self.warn)
|
||||||
# set up the build environment
|
# set up the build environment
|
||||||
self._init_env(freshenv)
|
self._init_env(freshenv)
|
||||||
# set up the builder
|
# set up the builder
|
||||||
|
@ -224,7 +224,7 @@ class Config(object):
|
|||||||
self.overrides = overrides
|
self.overrides = overrides
|
||||||
self.values = Config.config_values.copy()
|
self.values = Config.config_values.copy()
|
||||||
config = {}
|
config = {}
|
||||||
if 'extensions' in overrides:
|
if 'extensions' in overrides: #XXX do we need this?
|
||||||
if isinstance(overrides['extensions'], string_types):
|
if isinstance(overrides['extensions'], string_types):
|
||||||
config['extensions'] = overrides.pop('extensions').split(',')
|
config['extensions'] = overrides.pop('extensions').split(',')
|
||||||
else:
|
else:
|
||||||
@ -249,6 +249,30 @@ class Config(object):
|
|||||||
self.setup = config.get('setup', None)
|
self.setup = config.get('setup', None)
|
||||||
self.extensions = config.get('extensions', [])
|
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__ + (type(current),))
|
||||||
|
& 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):
|
def check_unicode(self, warn):
|
||||||
# check all string values for non-ASCII characters in bytestrings,
|
# check all string values for non-ASCII characters in bytestrings,
|
||||||
# since that can result in UnicodeErrors all over the place
|
# since that can result in UnicodeErrors all over the place
|
||||||
@ -296,7 +320,6 @@ class Config(object):
|
|||||||
for name in config:
|
for name in config:
|
||||||
if name in self.values:
|
if name in self.values:
|
||||||
self.__dict__[name] = config[name]
|
self.__dict__[name] = config[name]
|
||||||
del self._raw_config
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if name.startswith('_'):
|
if name.startswith('_'):
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
:copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
|
:copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
|
||||||
:license: BSD, see LICENSE for details.
|
:license: BSD, see LICENSE for details.
|
||||||
"""
|
"""
|
||||||
from six import PY3
|
from six import PY2, PY3, StringIO
|
||||||
|
|
||||||
from util import TestApp, with_app, with_tempdir, raises, raises_msg
|
from util import TestApp, with_app, with_tempdir, raises, raises_msg
|
||||||
|
|
||||||
@ -133,3 +133,41 @@ def test_config_eol(tmpdir):
|
|||||||
cfg = Config(tmpdir, 'conf.py', {}, None)
|
cfg = Config(tmpdir, 'conf.py', {}, None)
|
||||||
cfg.init_values(lambda warning: 1/0)
|
cfg.init_values(lambda warning: 1/0)
|
||||||
assert cfg.project == u'spam'
|
assert cfg.project == u'spam'
|
||||||
|
|
||||||
|
|
||||||
|
TYPECHECK_OVERRIDES = [
|
||||||
|
# configuration key, override value, should warn, default type
|
||||||
|
('master_doc', 123, True, str),
|
||||||
|
('man_pages', 123, True, list), # lambda
|
||||||
|
('man_pages', [], False, list),
|
||||||
|
('epub_tocdepth', True, True, int), # child type
|
||||||
|
('nitpicky', 3, False, bool), # parent type
|
||||||
|
('templates_path', (), True, list), # other sequence, also raises
|
||||||
|
]
|
||||||
|
if PY2:
|
||||||
|
# Run a check for proper sibling detection in Python 2. Under py3k, the
|
||||||
|
# default types do not have any siblings.
|
||||||
|
TYPECHECK_OVERRIDES.append(
|
||||||
|
('html_add_permalinks', 'bar', False, unicode))
|
||||||
|
|
||||||
|
def test_gen_check_types():
|
||||||
|
for key, value, should, deftype in TYPECHECK_OVERRIDES:
|
||||||
|
warning = StringIO()
|
||||||
|
try:
|
||||||
|
app = TestApp(confoverrides={key: value}, warning=warning)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
app.cleanup()
|
||||||
|
|
||||||
|
real = type(value).__name__
|
||||||
|
msg = ("WARNING: the config value %r has type `%s',"
|
||||||
|
" defaults to `%s.'\n" % (key, real, deftype.__name__))
|
||||||
|
def test():
|
||||||
|
assert (msg in warning.buflist) == should, \
|
||||||
|
"Setting %s to %r should%s raise: %s" % \
|
||||||
|
(key, value, " not" if should else "", msg)
|
||||||
|
test.description = "test_check_type_%s_on_%s" % \
|
||||||
|
(real, type(Config.config_values[key][0]).__name__)
|
||||||
|
|
||||||
|
yield test
|
||||||
|
Loading…
Reference in New Issue
Block a user