Skip to content

Commit 2439128

Browse files
authored
Merge pull request #1973 from Exirel/config-unexpected-settings
config: warn if unexpected settings exist
2 parents 3a81b0c + 35e4916 commit 2439128

File tree

4 files changed

+91
-7
lines changed

4 files changed

+91
-7
lines changed

sopel/bot.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ def setup(self):
254254
"""
255255
self.setup_logging()
256256
self.setup_plugins()
257-
self._scheduler.start()
257+
self.post_setup()
258258

259259
def setup_logging(self):
260260
"""Set up logging based on config options."""
@@ -326,6 +326,34 @@ def setup_plugins(self):
326326
else:
327327
LOGGER.warning("Warning: Couldn't load any plugins")
328328

329+
# post setup
330+
331+
def post_setup(self):
332+
"""Perform post-setup actions.
333+
334+
This method handles everything that should happen after all the plugins
335+
are loaded, and before the bot can connect to the IRC server.
336+
337+
At the moment, this method checks for undefined configuration options,
338+
and starts the job scheduler.
339+
340+
.. versionadded:: 7.1
341+
"""
342+
settings = self.settings
343+
for section_name, section in settings.get_defined_sections():
344+
for option_name in settings.parser.options(section_name):
345+
if not hasattr(section, option_name):
346+
LOGGER.warning(
347+
'Config option `%s.%s` is not defined by its section '
348+
'and may not be recognized by Sopel.',
349+
section_name,
350+
option_name,
351+
)
352+
353+
self._scheduler.start()
354+
355+
# plugins management
356+
329357
def reload_plugin(self, name):
330358
"""Reload a plugin.
331359
@@ -452,6 +480,8 @@ def get_plugin_meta(self, name):
452480

453481
return self._plugins[name].get_meta_description()
454482

483+
# callable management
484+
455485
@deprecated(
456486
reason="Replaced by specific `unregister_*` methods.",
457487
version='7.1',
@@ -609,6 +639,8 @@ def msg(self, recipient, text, max_messages=1):
609639
"""
610640
self.say(text, recipient, max_messages)
611641

642+
# message dispatch
643+
612644
def call_rule(self, rule, sopel, trigger):
613645
# rate limiting
614646
if not trigger.admin and not rule.is_unblockable():
@@ -856,6 +888,8 @@ def _update_running_triggers(self, running_triggers):
856888
self._running_triggers = [
857889
t for t in running_triggers if t.is_alive()]
858890

891+
# event handlers
892+
859893
def on_scheduler_error(self, scheduler, exc):
860894
"""Called when the Job Scheduler fails.
861895
@@ -969,6 +1003,8 @@ def _shutdown(self):
9691003
# Avoid calling shutdown methods if we already have.
9701004
self.shutdown_methods = []
9711005

1006+
# URL callbacks management
1007+
9721008
def register_url_callback(self, pattern, callback):
9731009
"""Register a ``callback`` for URLs matching the regex ``pattern``.
9741010

sopel/config/__init__.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,30 @@ def homedir(self):
159159
else:
160160
return os.path.dirname(self.filename)
161161

162+
def get_defined_sections(self):
163+
"""Retrieve all defined static sections of this configuration.
164+
165+
:return: all instances of :class:`~sopel.config.types.StaticSection`
166+
defined for this configuration file
167+
:rtype: list
168+
169+
When a plugin defines a section (using :meth:`define_section`), it
170+
associates a :class:`~sopel.config.types.StaticSection` for the section.
171+
This method allows to retrieve these instances of ``StaticSection``,
172+
and only these.
173+
174+
.. versionadded:: 7.1
175+
"""
176+
sections = (
177+
(name, getattr(self, name))
178+
for name in self.parser.sections()
179+
)
180+
return [
181+
(name, section)
182+
for name, section in sections
183+
if isinstance(section, types.StaticSection)
184+
]
185+
162186
def save(self):
163187
"""Write all changes to the config file.
164188

sopel/config/types.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,23 @@ def __init__(self, config, section_name, validate=True):
6666
self._parent = config
6767
self._parser = config.parser
6868
self._section_name = section_name
69+
6970
for value in dir(self):
71+
if value in ('_parent', '_parser', '_section_name'):
72+
# ignore internal attributes
73+
continue
74+
7075
try:
7176
getattr(self, value)
7277
except ValueError as e:
7378
raise ValueError(
74-
'Invalid value for {}.{}: {}'.format(section_name, value,
75-
str(e))
76-
)
79+
'Invalid value for {}.{}: {}'.format(
80+
section_name, value, str(e)))
7781
except AttributeError:
7882
if validate:
7983
raise ValueError(
80-
'Missing required value for {}.{}'.format(section_name,
81-
value)
82-
)
84+
'Missing required value for {}.{}'.format(
85+
section_name, value))
8386

8487
def configure_setting(self, name, prompt, default=NO_DEFAULT):
8588
"""Return a validated value for this attribute from the terminal.

test/test_config.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
"#startquote
3737
&endquote"
3838
"&quoted"
39+
40+
[somesection]
41+
is_defined = no
3942
""" # noqa (trailing whitespaces are intended)
4043

4144
TEST_CHANNELS = [
@@ -343,3 +346,21 @@ def test_save_modified_config(multi_fakeconfig):
343346
'&endquote"',
344347
'"&quoted"', # doesn't start with a # so it isn't escaped
345348
]
349+
350+
351+
def test_get_defined_sections(multi_fakeconfig):
352+
assert multi_fakeconfig.parser.has_section('core')
353+
assert multi_fakeconfig.parser.has_section('fake')
354+
assert multi_fakeconfig.parser.has_section('spam')
355+
assert multi_fakeconfig.parser.has_section('somesection')
356+
357+
results = multi_fakeconfig.get_defined_sections()
358+
359+
assert len(results) == 3, 'There should be 3 defined sections'
360+
361+
items = dict(results)
362+
assert 'core' in items, 'core must be always defined'
363+
assert 'fake' in items
364+
assert 'spam' in items
365+
assert 'somesection' not in items, (
366+
'somesection was not defined and should not appear as such')

0 commit comments

Comments
 (0)