Skip to content

Commit

Permalink
python 3.7 friendly
Browse files Browse the repository at this point in the history
  • Loading branch information
mazborowski committed Oct 20, 2023
1 parent 71f6d63 commit ca078e2
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 102 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -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 =
Expand Down
57 changes: 28 additions & 29 deletions src/quickhost/Cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
50 changes: 27 additions & 23 deletions src/quickhost/QuickhostPlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,15 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.

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

Expand All @@ -35,47 +42,44 @@ 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-<plugin_name>`, 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()()
elif plugin_type == 'parser':
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)
34 changes: 16 additions & 18 deletions src/quickhost/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
58 changes: 30 additions & 28 deletions src/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,51 +48,53 @@ 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)

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()
Expand Down
1 change: 0 additions & 1 deletion tests/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import pytest
from quickhost import QHPlugin
from quickhost_null import NullApp, NullParser

Expand Down

0 comments on commit ca078e2

Please sign in to comment.