diff --git a/README.md b/README.md index d98d6cd..aca23e7 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,29 @@ options: ## ~~Build~~ +### Python 3.7, 3.8 + +i dunno tox :( + + +install Python, e.g. + +`sudo apt-get install python3.8` + +install pip + +``` +sudo apt install python3.8-distutils +curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py +python3.8 get-pip.py +``` + +and your choice of venv + +``` +sudo apt install python3.8-venv +``` + ### ~~pyinstaller~~ #### do this diff --git a/setup.cfg b/setup.cfg index fa01d0e..4bcf765 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,11 @@ [metadata] name = quickhost -version = 0.0.2 +version = 1.0.0 [options] -python_requires = >=3.10 +python_requires = >=3.7 install_requires = - importlib-metadata; python_version>="3.10" + importlib-metadata; python_version>="3.7" [options.entry_points] console_scripts = diff --git a/src/quickhost/Cli.py b/src/quickhost/Cli.py index 49003e0..8237349 100644 --- a/src/quickhost/Cli.py +++ b/src/quickhost/Cli.py @@ -105,36 +105,35 @@ def cli(plugin_name: str) -> CliResponse: args = vars(plugin_parser.parse_args(plugin_args)) action = args.pop(plugin_name) - logger.debug(f"{action=}") + logger.debug("action={}".format(action)) app_class: AppBase = plugin.app app_instance: AppBase = app_class(plugin.name) # pyright: ignore - match action: - case 'init': - return app_instance.plugin_init(args) # pyright: ignore - case 'make': - return app_instance.create(args) # pyright: ignore - case 'describe': - return app_instance.describe(args) # pyright: ignore - case 'destroy': - return app_instance.destroy(args) # pyright: ignore - case 'update': - return app_instance.update(args) # pyright: ignore - case 'list-all': - return app_class.list_all() # pyright: ignore - case 'destroy-all': - return app_class.destroy_all() # pyright: ignore - case 'destroy-plugin': - return app_instance.plugin_destroy(args) # pyright: ignore - case 'help': - # @@@want: something for the Cobra-style commander users - app_parser.print_help() - return CliResponse('', 'For help on a specific action, use -h.', 1) - case None: - # unreachable - app_parser.print_help() - return CliResponse('', "Bug! action was 'None'", 1) - case _: - app_parser.print_help() - return CliResponse('', f"Invalid action: '{action}'", 1) + if action == 'init': + return app_instance.plugin_init(args) # pyright: ignore + elif action == 'make': + return app_instance.create(args) # pyright: ignore + elif action == 'describe': + return app_instance.describe(args) # pyright: ignore + elif action == 'destroy': + return app_instance.destroy(args) # pyright: ignore + elif action == 'update': + return app_instance.update(args) # pyright: ignore + elif action == 'list-all': + return app_class.list_all() # pyright: ignore + elif action == 'destroy-all': + return app_class.destroy_all() # pyright: ignore + elif action == 'destroy-plugin': + return app_instance.plugin_destroy(args) # pyright: ignore + elif action == 'help': + # @@@want: something for the Cobra-style commander users + app_parser.print_help() + return CliResponse('', 'For help on a specific action, use -h.', 1) + elif action is None: + # unreachable + app_parser.print_help() + return CliResponse('', "Bug! action was 'None'", 1) + else: + app_parser.print_help() + return CliResponse('', f"Invalid action: '{action}'", 1) diff --git a/src/quickhost/QuickhostPlugin.py b/src/quickhost/QuickhostPlugin.py index 762d50f..d289313 100644 --- a/src/quickhost/QuickhostPlugin.py +++ b/src/quickhost/QuickhostPlugin.py @@ -14,8 +14,15 @@ # along with this program. If not, see . import logging -from importlib import metadata import sys + +if sys.version_info.minor == 7: + import pkgutil +else: + from importlib import metadata + +import typing as t + from collections import defaultdict from dataclasses import dataclass @@ -35,39 +42,36 @@ class Plugin: class QHPlugin: - """ - for python < 3.10 - https://bugs.python.org/issue44246 - https://github.com/python/importlib_metadata/pull/278/files# - """ @classmethod - def try_load_plugin(cls, plugin_name: str) -> Plugin: - """ - returns a plugin - - This is meant to be used by plugins to provide a shortcut script, - `quickhost-`, so we know ahead of time which plugin we want to try - and load, and that it will definitely exist. - """ - plugin_groups = metadata.entry_points().select(group='quickhost_plugin') - app = plugin_groups.select(name='pve_app')[plugin_name + "_app"].load()() - parser = plugin_groups.select(name='pve_parser')[plugin_name + "_parser"].load()() - return Plugin(plugin_name, app, parser) - - @classmethod - def load_all_plugins(cls) -> dict[str, Plugin]: + def load_all_plugins(cls) -> t.Dict[str, Plugin]: """returns a dictionary mapping installed plugin names to a Plugin object""" plugins = defaultdict(dict) - if sys.version_info.minor < 10: + if sys.version_info.minor == 7: + plugins = defaultdict(dict) + for p in pkgutil.walk_packages(): + if p.name.startswith('quickhost_') and p.ispkg: + l = pkgutil.get_loader(p.name).load_module() # noqa: E741 + # found in plugin's __init__.py + provider_name = p.name.split('_')[1] + app = l.load_plugin() + parser = l.get_parser() + plugins[provider_name] = Plugin(name=provider_name, app=app, parser=parser) + + return dict(plugins) + + elif sys.version_info.minor > 7 and sys.version_info.minor < 10: plugin_parsers = metadata.entry_points()['quickhost_plugin'] else: plugin_parsers = metadata.entry_points().select(group="quickhost_plugin") # sift through plugins, organize by cloud provider and return for p in plugin_parsers: + # see plugin's setup.py + # 'aws' provider_name = p.name.split('_')[0] + # 'app' or 'parser' plugin_type = p.name.split('_')[1] if plugin_type == 'app': plugins[provider_name]['app'] = p.load()() @@ -75,7 +79,7 @@ def load_all_plugins(cls) -> dict[str, Plugin]: plugins[provider_name]['parser'] = p.load()() else: logger.warning(f"Unknown plugin type '{plugin_type}'") - plugins_list: dict[str, Plugin] = { + plugins_list: t.Dict[str, Plugin] = { p: Plugin(p, plugins[p]['app'], plugins[p]['parser']) for p in plugins.keys() # noqa: E126 } return dict(plugins_list) diff --git a/src/quickhost/utilities.py b/src/quickhost/utilities.py index b08f294..e4d5bfd 100644 --- a/src/quickhost/utilities.py +++ b/src/quickhost/utilities.py @@ -69,23 +69,21 @@ def __init__(self, color=False): def format(self, record): # orig_fmt = self._style._fmt if self.colored_output: - match record.levelno: - case logging.DEBUG: - self._style._fmt = QHLogFormatter.DebugFormatColor - case logging.INFO: - self._style._fmt = QHLogFormatter.InfoFormatColor - case logging.WARNING: - self._style._fmt = QHLogFormatter.WarningFormatColor - case (logging.ERROR | logging.CRITICAL): - self._style._fmt = QHLogFormatter.ErrorFormatColor + if record.levelno == logging.DEBUG: + self._style._fmt = QHLogFormatter.DebugFormatColor + elif record.levelno == logging.INFO: + self._style._fmt = QHLogFormatter.InfoFormatColor + elif record.levelno == logging.WARNING: + self._style._fmt = QHLogFormatter.WarningFormatColor + elif record.levelno == (logging.ERROR | logging.CRITICAL): + self._style._fmt = QHLogFormatter.ErrorFormatColor else: - match record.levelno: - case logging.DEBUG: - self._style._fmt = QHLogFormatter.DebugFormat - case logging.INFO: - self._style._fmt = QHLogFormatter.InfoFormat - case logging.WARNING: - self._style._fmt = QHLogFormatter.WarningFormat - case (logging.ERROR | logging.CRITICAL): - self._style._fmt = QHLogFormatter.ErrorFormat + if record.levelno == logging.DEBUG: + self._style._fmt = QHLogFormatter.DebugFormat + elif record.levelno == logging.INFO: + self._style._fmt = QHLogFormatter.InfoFormat + elif record.levelno == logging.WARNING: + self._style._fmt = QHLogFormatter.WarningFormat + elif record.levelno == (logging.ERROR | logging.CRITICAL): + self._style._fmt = QHLogFormatter.ErrorFormat return super().format(record) diff --git a/src/scripts/main.py b/src/scripts/main.py index 82c9431..26507c9 100755 --- a/src/scripts/main.py +++ b/src/scripts/main.py @@ -48,15 +48,18 @@ def cli_main() -> CliResponse: logger.debug("cli args={}".format(args)) if args['version']: - from importlib.metadata import version - return CliResponse(version('quickhost'), '', 0) + if sys.version_info.minor > 7: + from importlib.metadata import version + return CliResponse(version('quickhost'), '', 0) + else: + return CliResponse('', 'package info not available for Python {}.{}'.format(sys.version_info.major, sys.version_info.minor), 1) if dict(plugins) == {}: app_parser.print_help() return CliResponse('', "No plugins are installed! Try pip install --user quickhost-aws", 1) tgt_plugin = args.pop('plugin') - logger.debug(f"{tgt_plugin=}") + logger.debug(f"{tgt_plugin}") if tgt_plugin is None: app_parser.print_help() return CliResponse('', f"Provide a plugin {[k for k in plugins.keys()]}", 1) @@ -64,35 +67,34 @@ def cli_main() -> CliResponse: app_name = None # @@@ if 'app_name' in args.keys(): # @@@ app_name = args.pop("app_name") # @@@ - logger.debug(f"{app_name=}") + logger.debug("app_name={}".format(app_name)) action = args.pop(tgt_plugin) - logger.debug(f"{action=}") + logger.debug("action={}".format(action)) app_class: AppBase = plugins[tgt_plugin].app app_instance: Type[AppBase] = app_class(app_name) # pyright: ignore - match action: - case 'init': - return app_instance.plugin_init(args) # pyright: ignore - case 'make': - return app_instance.create(args) # pyright: ignore - case 'describe': - return app_instance.describe(args) # pyright: ignore - case 'destroy': - return app_instance.destroy(args) # pyright: ignore - case 'update': - return app_instance.update(args) # pyright: ignore - case 'list-all': - return app_class.list_all() # pyright: ignore - case 'destroy-all': - return app_class.destroy_all() # pyright: ignore - case 'destroy-plugin': - return app_instance.plugin_destroy(args) # pyright: ignore - case None: - app_parser.print_help() - return CliResponse('', f"No action provided (try quickhost {tgt_plugin} -h)", 1) - case _: - app_parser.print_help() - return CliResponse('', f"Invalid action: '{action}'", 1) + if action == 'init': + return app_instance.plugin_init(args) # pyright: ignore + elif action == 'make': + return app_instance.create(args) # pyright: ignore + elif action == 'describe': + return app_instance.describe(args) # pyright: ignore + elif action == 'destroy': + return app_instance.destroy(args) # pyright: ignore + elif action == 'update': + return app_instance.update(args) # pyright: ignore + elif action == 'list-all': + return app_class.list_all() # pyright: ignore + elif action == 'destroy-all': + return app_class.destroy_all() # pyright: ignore + elif action == 'destroy-plugin': + return app_instance.plugin_destroy(args) # pyright: ignore + elif action is None: + app_parser.print_help() + return CliResponse('', f"No action provided (try quickhost {tgt_plugin} -h)", 1) + else: + app_parser.print_help() + return CliResponse('', f"Invalid action: '{action}'", 1) fd1, fd2, rc = cli_main() diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 13044d2..1fb13e9 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -12,7 +12,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import pytest from quickhost import QHPlugin from quickhost_null import NullApp, NullParser