Allow plugins to use relative imports to avoid conflicts between modules with the same name from another plugin. Fixes #2345 diff --git a/plug.py b/plug.py index 1a8779f7..8890c80f 100644 --- a/plug.py +++ b/plug.py @@ -48,13 +48,20 @@ last_error = LastError() class Plugin: """An EDMC plugin.""" - def __init__(self, name: str, loadfile: Path | None, plugin_logger: logging.Logger | None): # noqa: CCR001 + def __init__( + self, + name: str, + loadfile: Path | None, + plugin_logger: logging.Logger | None, + internal: bool = False + ): # noqa: CCR001 """ Load a single plugin. :param name: Base name of the file being loaded from. :param loadfile: Full path/filename of the plugin. :param plugin_logger: The logging instance for this plugin to use. + :param internal: True to use internal plugin loader logic. Defaults to False. :raises Exception: Typically, ImportError or OSError """ self.name: str = name # Display name. @@ -65,14 +72,19 @@ class Plugin: if loadfile: logger.info(f'loading plugin "{name.replace(".", "_")}" from "{loadfile}"') try: - filename = 'plugin_' - filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_') - spec = importlib.util.spec_from_file_location(filename, loadfile) - # Replaces older load_module() code. Includes a safety check that the module name is set. - if spec is not None and spec.loader is not None: - module = importlib.util.module_from_spec(spec) - sys.modules[module.__name__] = module - spec.loader.exec_module(module) + if internal: + filename = 'plugin_' + filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_') + spec = importlib.util.spec_from_file_location(filename, loadfile) + # Replaces older load_module() code. Includes a safety check that the module name is set. + if spec is not None and spec.loader is not None: + module = importlib.util.module_from_spec(spec) + sys.modules[module.__name__] = module + spec.loader.exec_module(module) + else: + module = importlib.import_module('.load', name) + + if module: if getattr(module, 'plugin_start3', None): newname = module.plugin_start3(Path(loadfile).resolve().parent) self.name = str(newname) if newname else self.name @@ -170,7 +182,7 @@ def _load_internal_plugins(): try: plugin_name = name[:-3] plugin_path = config.internal_plugin_dir_path / name - plugin = Plugin(plugin_name, plugin_path, logger) + plugin = Plugin(plugin_name, plugin_path, logger, internal=True) plugin.folder = None internal.append(plugin) except Exception: diff --git a/plug.py b/plug.py index 1a8779f7..8890c80f 100644 --- a/plug.py +++ b/plug.py @@ -48,13 +48,20 @@ last_error = LastError() class Plugin: """An EDMC plugin.""" - def __init__(self, name: str, loadfile: Path | None, plugin_logger: logging.Logger | None): # noqa: CCR001 + def __init__( + self, + name: str, + loadfile: Path | None, + plugin_logger: logging.Logger | None, + internal: bool = False + ): # noqa: CCR001 """ Load a single plugin. :param name: Base name of the file being loaded from. :param loadfile: Full path/filename of the plugin. :param plugin_logger: The logging instance for this plugin to use. + :param internal: True to use internal plugin loader logic. Defaults to False. :raises Exception: Typically, ImportError or OSError """ self.name: str = name # Display name. @@ -65,14 +72,19 @@ class Plugin: if loadfile: logger.info(f'loading plugin "{name.replace(".", "_")}" from "{loadfile}"') try: - filename = 'plugin_' - filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_') - spec = importlib.util.spec_from_file_location(filename, loadfile) - # Replaces older load_module() code. Includes a safety check that the module name is set. - if spec is not None and spec.loader is not None: - module = importlib.util.module_from_spec(spec) - sys.modules[module.__name__] = module - spec.loader.exec_module(module) + if internal: + filename = 'plugin_' + filename += name.encode(encoding='ascii', errors='replace').decode('utf-8').replace('.', '_') + spec = importlib.util.spec_from_file_location(filename, loadfile) + # Replaces older load_module() code. Includes a safety check that the module name is set. + if spec is not None and spec.loader is not None: + module = importlib.util.module_from_spec(spec) + sys.modules[module.__name__] = module + spec.loader.exec_module(module) + else: + module = importlib.import_module('.load', name) + + if module: if getattr(module, 'plugin_start3', None): newname = module.plugin_start3(Path(loadfile).resolve().parent) self.name = str(newname) if newname else self.name @@ -170,7 +182,7 @@ def _load_internal_plugins(): try: plugin_name = name[:-3] plugin_path = config.internal_plugin_dir_path / name - plugin = Plugin(plugin_name, plugin_path, logger) + plugin = Plugin(plugin_name, plugin_path, logger, internal=True) plugin.folder = None internal.append(plugin) except Exception:
Any questions or offers of help can be directed to the EDCD Discord #edmc channel:
Elite: Dangerous Market Connector (EDMC)
This application is only of use to PC players of the game Elite Dangerous (and its expansions). It won't work with PS4 or Xbox accounts.
It utilises the Journal files written by the game on the user's computer, together with data from the API Frontier Developments supplies in order to feed this data to various third party sites that the user may find useful.
See the Wiki documentation for more details.
Installation & Uninstall
Please see the Installation & Setup wiki page.
Running from source
Please see the Running from source wiki page.
Plugins
Plugins extend the behaviour of this app. See the Plugins wiki page for more information.
If you would like to write a plugin please see PLUGINS.md.
Troubleshooting
Please see the Troubleshooting wiki page.
Reporting a problem
Please report a problem as a new GitHub issue. See Reporting a problem for further guidance, including how to find the necessary log files to attach to the report.
Packaging for distribution
Please see docs/Releasing.md.
Disclaimer
This app uses the “Companion” web API that Frontier originally supplied for their Elite Dangerous iOS app and now support for third-party apps. If that API ceases to function in the future then much of this application's functionality will be curtailed (although it could still utilise Journal files).
Acknowledgements
Please see the Acknowledgements wiki page.
License
Copyright © 2015-2019 Jonathan Harris, 2020-2024 EDCD
Licensed under the GNU Public License (GPL) version 2 or later.