python installer #236
Replies: 3 comments 1 reply
-
@dieBakterie I'll push a local branch soon. The main idea here is upon HyDE installation/updates we will prepare a Venv. And all hydepy scripts will use that as its environment. It's all good except for the fact that it's slow on loading the click module (<250 ms to load). I already discussed this stuff with @rubiin. Hehe Thanks @rubiin . I'm planning this ao we can easily integrate the hydepanel.😉 Here's a snippit hydepy (entrypoint)import os
import sys
import click
import importlib.util
# Add the current directory to the path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
from utils import fmt_logger, get_venv_path # noqa: E402
except ImportError:
print("Error: Cannot import lib/utils.py")
print("Is hyde installed correctly?")
sys.exit(1)
log = fmt_logger()
sys.path.insert(0, get_venv_path())
module_cache = {}
module_dirs = [
os.path.expanduser("~/.local/lib/hydepy/modules"),
"/usr/local/lib/hydepy/modules",
"/usr/lib/hydepy/modules",
]
def list_modules():
modules = []
for module_dir in module_dirs:
if os.path.exists(module_dir):
sys.path.insert(0, module_dir)
for filename in os.listdir(module_dir):
if filename.endswith(".py") and filename != "__init__.py":
module_name = filename[:-3]
try:
module = importlib.import_module(module_name)
if hasattr(module, "hyde") and callable(
getattr(module, "hyde")
):
modules.append(module_name)
except ImportError as e:
log.error(f"Error importing module {module_name}: {e}")
click.echo(f"Error importing module {module_name}: {e}")
sys.path.pop(0)
return modules
class LazyGroup(click.Group):
"""Lazy Load the cached modules, this is to avoid loading all modules at once = faster"""
def list_commands(self, ctx):
return list_modules()
def get_command(self, ctx, cmd_name):
if cmd_name in module_cache:
return module_cache[cmd_name]
for module_dir in module_dirs:
module_path = os.path.join(module_dir, f"{cmd_name}.py")
if os.path.exists(module_path):
try:
spec = importlib.util.spec_from_file_location(cmd_name, module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
module_cache[cmd_name] = module
@click.command(
name=cmd_name,
context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
help_option_names=[], # Disable the default --help option
),
help=module.hyde.__doc__
if hasattr(module, "hyde")
else f"Help for {cmd_name}",
)
@click.pass_context
def _command(ctx):
try:
module.hyde(ctx.args)
except Exception as e:
log.error(f"Error executing command {cmd_name}: {e}")
click.echo(f"Error executing command {cmd_name}: {e}")
return _command
except Exception as e:
log.error(f"Error loading module {cmd_name}: {e}")
click.echo(f"Error loading module {cmd_name}: {e}")
return None
@click.command(cls=LazyGroup, invoke_without_command=True)
@click.pass_context
def cli(ctx):
log.debug(f"Starting hydepy... Using logger: {log.get_logger_type()}")
ctx.ensure_object(dict)
if ctx.invoked_subcommand is None:
click.echo(ctx.get_help())
ctx.exit()
if __name__ == "__main__":
cli()
os._exit utils.pyimport os
import sys
import importlib
def get_venv_path():
"""Set up the virtual environment path and modify sys.path."""
# Determine the virtual environment path using XDG Base Directory Specification
xdg_state_home = os.getenv("XDG_STATE_HOME", os.path.expanduser("~/.local/state"))
venv_path = os.path.join(xdg_state_home, "hydepy", "venv")
# Fall back to $HOME/.local/lib/hyde/venv if XDG_state_HOME is not set
if not os.path.exists(venv_path):
venv_path = os.path.expanduser("~/.local/state/hydepy/venv")
site_packages_path = os.path.join(
venv_path,
"lib",
f"python{sys.version_info.major}.{sys.version_info.minor}",
"site-packages",
)
sys.path.insert(0, site_packages_path)
return venv_path
def fmt_logger():
"""Format logger to provide a unified interface."""
log_level = os.getenv("LOG_LEVEL")
if not log_level:
class NoOpLogger:
def __getattr__(self, name):
def no_op(*args, **kwargs):
pass
return no_op
def get_logger_type(self):
return "NoOpLogger"
return NoOpLogger()
log_level = log_level.upper()
# Dynamically import logging or loguru
try:
log = importlib.import_module("loguru")
log.logger.remove()
log.logger.add(sys.stderr, level=log_level)
logger_type = "loguru"
except ModuleNotFoundError:
import logging as log
log.basicConfig(level=getattr(log, log_level))
logger_type = "logging"
class UnifiedLogger:
def __init__(self, logger, logger_type):
self.logger = logger
self.logger_type = logger_type
def debug(self, msg, *args, **kwargs):
if hasattr(self.logger, "logger"):
self.logger.logger.debug(msg, *args, **kwargs)
else:
self.logger.debug(msg, *args, **kwargs)
def info(self, msg, *args, **kwargs):
if hasattr(self.logger, "logger"):
self.logger.logger.info(msg, *args, **kwargs)
else:
self.logger.info(msg, *args, **kwargs)
def warning(self, msg, *args, **kwargs):
if hasattr(self.logger, "logger"):
self.logger.logger.warning(msg, *args, **kwargs)
else:
self.logger.warning(msg, *args, **kwargs)
def error(self, msg, *args, **kwargs):
if hasattr(self.logger, "logger"):
self.logger.logger.error(msg, *args, **kwargs)
else:
self.logger.error(msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs):
if hasattr(self.logger, "logger"):
self.logger.logger.critical(msg, *args, **kwargs)
else:
self.logger.critical(msg, *args, **kwargs)
def get_logger_type(self):
return self.logger_type
return UnifiedLogger(log, logger_type)
def load_modules(module_dir):
modules = []
sys.path.insert(0, module_dir)
for filename in os.listdir(module_dir):
if filename.endswith(".py") and filename != "__init__.py":
module_name = filename[:-3]
try:
module = importlib.import_module(module_name)
if hasattr(module, "hyde") and callable(getattr(module, "hyde")):
modules.append(module_name)
except ImportError:
continue
sys.path.pop(0)
return modules
def load_module(module_dir, command):
sys.path.insert(0, module_dir)
try:
module = importlib.import_module(command)
except ModuleNotFoundError:
module = None
sys.path.pop(0)
return module
# Call get_venv_path() to set up the virtual environment path
sys.path.insert(0, get_venv_path()) I'll be very grateful as I can see that average HyDE users know a bunch of python. |
Beta Was this translation helpful? Give feedback.
-
#236 (comment) |
Beta Was this translation helpful? Give feedback.
-
Wanna keep and use this discussion for planning the migration and pre-work needed for python transfer? Like which/what libs we use, when we write our own code and when we use built-in and when external libs... |
Beta Was this translation helpful? Give feedback.
-
Idea how to migrate from shell and bash to python
First we should make an helper script with the
fmt_logging
and other stuff that's used in all scripts, like withglobalcontrol.sh
right now for the shell/bash scripts/installer.Example:
globalcontrol.py
Then in
parse.json.py
we do:could be that we've to run the
globalcontrol.py
script in the python scripts like for theshell/bash
scripts via subprocess or we run the scripts(parse.json.py
, ...) via subprocess from theglobalcontrol.py
via args like--json
,--config
, ...Beta Was this translation helpful? Give feedback.
All reactions