Skip to content

Commit b0693b7

Browse files
SnoopJdgw
authored andcommitted
plugins.handlers: handle cases where importlib may return None
1 parent a622d7c commit b0693b7

File tree

1 file changed

+28
-17
lines changed

1 file changed

+28
-17
lines changed

sopel/plugins/handlers.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161

6262
if TYPE_CHECKING:
6363
from sopel.bot import Sopel
64+
from types import ModuleType
6465

6566

6667
class AbstractPluginHandler(abc.ABC):
@@ -259,6 +260,12 @@ def __init__(self, name, package=None):
259260

260261
self._module = None
261262

263+
@property
264+
def module(self) -> ModuleType:
265+
if self._module is None:
266+
raise RuntimeError('No module for plugin %s' % self.name)
267+
return self._module
268+
262269
def get_label(self):
263270
"""Retrieve a display label for the plugin.
264271
@@ -269,12 +276,11 @@ def get_label(self):
269276
docstring, its first line is used as the plugin's label.
270277
"""
271278
default_label = '%s plugin' % self.name
272-
module_doc = getattr(self._module, '__doc__', None)
273279

274-
if not self.is_loaded() or not module_doc:
280+
if not self.is_loaded() or not hasattr(self.module, '__doc__'):
275281
return default_label
276282

277-
lines = inspect.cleandoc(module_doc).splitlines()
283+
lines = inspect.cleandoc(self.module.__doc__).splitlines()
278284
return default_label if not lines else lines[0]
279285

280286
def get_meta_description(self):
@@ -317,8 +323,8 @@ def get_version(self) -> Optional[str]:
317323
:rtype: Optional[str]
318324
"""
319325
version: Optional[str] = None
320-
if hasattr(self._module, "__version__"):
321-
version = str(self._module.__version__)
326+
if self.is_loaded() and hasattr(self.module, "__version__"):
327+
version = str(self.module.__version__)
322328
elif self.module_name.startswith("sopel."):
323329
version = release
324330

@@ -336,14 +342,14 @@ def reload(self):
336342
337343
This method assumes the plugin is already loaded.
338344
"""
339-
self._module = importlib.reload(self._module)
345+
self._module = importlib.reload(self.module)
340346

341347
def is_loaded(self):
342348
return self._module is not None
343349

344350
def setup(self, bot):
345351
if self.has_setup():
346-
self._module.setup(bot)
352+
self.module.setup(bot)
347353

348354
def has_setup(self):
349355
"""Tell if the plugin has a setup action.
@@ -354,12 +360,12 @@ def has_setup(self):
354360
The plugin has a setup action if its module has a ``setup`` attribute.
355361
This attribute is expected to be a callable.
356362
"""
357-
return hasattr(self._module, 'setup')
363+
return hasattr(self.module, 'setup')
358364

359365
def get_capability_requests(self) -> List[plugin_decorators.capability]:
360366
return [
361367
module_attribute
362-
for module_attribute in vars(self._module).values()
368+
for module_attribute in vars(self.module).values()
363369
if isinstance(module_attribute, plugin_decorators.capability)
364370
]
365371

@@ -369,7 +375,7 @@ def register(self, bot: Sopel) -> None:
369375
bot.cap_requests.register(self.name, cap_request)
370376

371377
# plugin callables go through ``bot.add_plugin``
372-
relevant_parts = loader.clean_module(self._module, bot.config)
378+
relevant_parts = loader.clean_module(self.module, bot.config)
373379
for part in itertools.chain(*relevant_parts):
374380
# annotate all callables in relevant_parts with `plugin_name`
375381
# attribute to make per-channel config work; see #1839
@@ -379,12 +385,12 @@ def register(self, bot: Sopel) -> None:
379385
bot.add_plugin(self, *relevant_parts)
380386

381387
def unregister(self, bot):
382-
relevant_parts = loader.clean_module(self._module, bot.config)
388+
relevant_parts = loader.clean_module(self.module, bot.config)
383389
bot.remove_plugin(self, *relevant_parts)
384390

385391
def shutdown(self, bot):
386392
if self.has_shutdown():
387-
self._module.shutdown(bot)
393+
self.module.shutdown(bot)
388394

389395
def has_shutdown(self):
390396
"""Tell if the plugin has a shutdown action.
@@ -396,11 +402,11 @@ def has_shutdown(self):
396402
The plugin has a shutdown action if its module has a ``shutdown``
397403
attribute. This attribute is expected to be a callable.
398404
"""
399-
return hasattr(self._module, 'shutdown')
405+
return hasattr(self.module, 'shutdown')
400406

401407
def configure(self, settings):
402408
if self.has_configure():
403-
self._module.configure(settings)
409+
self.module.configure(settings)
404410

405411
def has_configure(self):
406412
"""Tell if the plugin has a configure action.
@@ -412,7 +418,7 @@ def has_configure(self):
412418
The plugin has a configure action if its module has a ``configure``
413419
attribute. This attribute is expected to be a callable.
414420
"""
415-
return hasattr(self._module, 'configure')
421+
return hasattr(self.module, 'configure')
416422

417423

418424
class PyFilePlugin(PyModulePlugin):
@@ -465,6 +471,9 @@ def __init__(self, filename):
465471
else:
466472
raise exceptions.PluginError('Invalid Sopel plugin: %s' % filename)
467473

474+
if spec is None:
475+
raise exceptions.PluginError('Could not determine spec for plugin: %s' % filename)
476+
468477
self.filename = filename
469478
self.path = filename
470479
self.module_spec = spec
@@ -474,6 +483,8 @@ def __init__(self, filename):
474483
def _load(self):
475484
module = importlib.util.module_from_spec(self.module_spec)
476485
sys.modules[self.name] = module
486+
if not self.module_spec.loader:
487+
raise exceptions.PluginError('Could not determine loader for plugin: %s' % self.filename)
477488
self.module_spec.loader.exec_module(module)
478489
return module
479490

@@ -595,9 +606,9 @@ def get_version(self) -> Optional[str]:
595606
"""
596607
version: Optional[str] = super().get_version()
597608

598-
if version is None and hasattr(self._module, "__package__"):
609+
if version is None and hasattr(self.module, "__package__"):
599610
try:
600-
version = importlib_metadata.version(self._module.__package__)
611+
version = importlib_metadata.version(self.module.__package__)
601612
except ValueError:
602613
# package name is probably empty-string; just give up
603614
pass

0 commit comments

Comments
 (0)