From f3c79609d852ca9f6ce1a31f65eb07b69094790b Mon Sep 17 00:00:00 2001 From: Nose Date: Thu, 1 Jan 2026 00:54:03 -0800 Subject: [PATCH] h --- medeia_macina/__init__.py | 9 -- medeia_macina/cli_entry.py | 276 ------------------------------------- scripts/__init__.py | 1 + scripts/bootstrap.ps1 | 14 +- scripts/bootstrap.py | 18 +-- scripts/bootstrap.sh | 26 ++-- scripts/cli_entry.py | Bin 0 -> 20210 bytes scripts/pyproject.toml | 4 +- 8 files changed, 32 insertions(+), 316 deletions(-) delete mode 100644 medeia_macina/__init__.py delete mode 100644 medeia_macina/cli_entry.py create mode 100644 scripts/__init__.py create mode 100644 scripts/cli_entry.py diff --git a/medeia_macina/__init__.py b/medeia_macina/__init__.py deleted file mode 100644 index 500a709..0000000 --- a/medeia_macina/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -"""Top-level package for Medeia-Macina. - -This package provides the `cli_entry` module which exposes the `main()` entry -point used by command-line launchers. -""" - -__all__ = ["cli_entry"] - -__version__ = "0.1.0" diff --git a/medeia_macina/cli_entry.py b/medeia_macina/cli_entry.py deleted file mode 100644 index 9c274f3..0000000 --- a/medeia_macina/cli_entry.py +++ /dev/null @@ -1,276 +0,0 @@ -"""Packaged CLI entrypoint used by installers and console scripts. - -This module provides the `main` entrypoint for `mm`/`medeia` and supports -running from a development checkout (by importing the top-level -`CLI.MedeiaCLI`) or when running tests that inject a legacy -`medeia_entry` shim into `sys.modules`. -""" - -from __future__ import annotations - -from typing import Optional, List, Tuple -import sys -import importlib -from pathlib import Path -import shlex - - -def _ensure_repo_root_on_sys_path(pkg_file: Optional[Path] = None) -> None: - """Ensure the repository root (where top-level modules live) is importable. - - The project currently keeps key modules like `CLI.py` at the repo root. - When `mm` is invoked from a different working directory, that repo root is - not necessarily on `sys.path`, which breaks `import CLI`. - - We infer the repo root by walking up from this package location and looking - for a sibling `CLI.py`. - - `pkg_file` exists for unit tests; production uses this module's `__file__`. - """ - try: - pkg_dir = (pkg_file or Path(__file__)).resolve().parent - except Exception: - return - - for parent in pkg_dir.parents: - try: - if (parent / "CLI.py").exists(): - parent_str = str(parent) - if parent_str not in sys.path: - sys.path.insert(0, parent_str) - return - except Exception: - continue - - -def _parse_mode_and_strip_args(args: List[str]) -> Tuple[Optional[str], List[str]]: - """Parse --gui/--cli/--mode flags and return (mode, cleaned_args). - - The function removes any mode flags from the argument list so the selected - runner can receive the remaining arguments untouched. - - Supported forms: - --gui, -g, --gui=true - --cli, -c, --cli=true - --mode=gui|cli - --mode gui|cli - - Raises ValueError on conflicting or invalid flags. - """ - mode: Optional[str] = None - out: List[str] = [] - i = 0 - while i < len(args): - a = args[i] - la = a.lower() - - # --gui / -g - if la in ("--gui", "-g"): - if mode and mode != "gui": - raise ValueError("Conflicting mode flags: found both 'gui' and 'cli'") - mode = "gui" - i += 1 - continue - if la.startswith("--gui="): - val = la.split("=", 1)[1] - if val and val not in ("0", "false", "no", "off"): - if mode and mode != "gui": - raise ValueError( - "Conflicting mode flags: found both 'gui' and 'cli'" - ) - mode = "gui" - i += 1 - continue - - # --cli / -c - if la in ("--cli", "-c"): - if mode and mode != "cli": - raise ValueError("Conflicting mode flags: found both 'gui' and 'cli'") - mode = "cli" - i += 1 - continue - if la.startswith("--cli="): - val = la.split("=", 1)[1] - if val and val not in ("0", "false", "no", "off"): - if mode and mode != "cli": - raise ValueError( - "Conflicting mode flags: found both 'gui' and 'cli'" - ) - mode = "cli" - i += 1 - continue - - # --mode - if la.startswith("--mode="): - val = la.split("=", 1)[1] - val = val.lower() - if val not in ("gui", "cli"): - raise ValueError("--mode must be 'gui' or 'cli'") - if mode and mode != val: - raise ValueError("Conflicting mode flags: found both 'gui' and 'cli'") - mode = val - i += 1 - continue - if la == "--mode": - if i + 1 >= len(args): - raise ValueError("--mode requires a value ('gui' or 'cli')") - val = args[i + 1].lower() - if val not in ("gui", "cli"): - raise ValueError("--mode must be 'gui' or 'cli'") - if mode and mode != val: - raise ValueError("Conflicting mode flags: found both 'gui' and 'cli'") - mode = val - i += 2 - continue - - # Not a mode flag; keep it - out.append(a) - i += 1 - - return mode, out - - -def _run_cli(clean_args: List[str]) -> int: - """Run the CLI runner (MedeiaCLI) with cleaned argv list. - - The function supports three modes (in order): - 1) If a `medeia_entry` module is present in sys.modules (for tests/legacy - scenarios), use it as the source of `MedeiaCLI`. - 2) If running from a development checkout, import the top-level `CLI` and - use `MedeiaCLI` from there. - 3) Otherwise fail with a helpful message suggesting an editable install. - """ - try: - sys.argv[1:] = list(clean_args) - except Exception: - pass - - # 1) Check for an in-memory 'medeia_entry' module (tests/legacy installs) - MedeiaCLI = None - if "medeia_entry" in sys.modules: - mod = sys.modules.get("medeia_entry") - if hasattr(mod, "MedeiaCLI"): - MedeiaCLI = getattr(mod, "MedeiaCLI") - else: - # Preserve the existing error message used by tests that inject a - # dummy 'medeia_entry' without a `MedeiaCLI` attribute. - raise ImportError( - "Imported module 'medeia_entry' does not define 'MedeiaCLI' and direct import of top-level 'CLI' failed.\n" - "Remedy: ensure the top-level 'medeia_entry' module exports 'MedeiaCLI' or run from the project root/debug the checkout." - ) - - # 2) If no in-memory module provided the class, try importing the repo-root CLI - if MedeiaCLI is None: - try: - _ensure_repo_root_on_sys_path() - from CLI import MedeiaCLI as _M # type: ignore - MedeiaCLI = _M - except Exception: - raise ImportError( - "Could not import 'MedeiaCLI'. This often means the project is not available on sys.path (run 'pip install -e scripts' or re-run the bootstrap script)." - ) - - try: - app = MedeiaCLI() - app.run() - return 0 - except SystemExit as exc: - return int(getattr(exc, "code", 0) or 0) - - -def _run_gui(clean_args: List[str]) -> int: - """Run the TUI runner (PipelineHubApp). - - The TUI is imported lazily; if Textual or the TUI code is unavailable we - give a helpful error message and exit non‑zero. - """ - try: - _ensure_repo_root_on_sys_path() - tui_mod = importlib.import_module("TUI.tui") - except Exception as exc: - print( - "Error: Unable to import TUI (Textual may not be installed):", - exc, - file=sys.stderr, - ) - return 2 - - try: - PipelineHubApp = getattr(tui_mod, "PipelineHubApp") - except AttributeError: - print("Error: 'TUI.tui' does not expose 'PipelineHubApp'", file=sys.stderr) - return 2 - - try: - app = PipelineHubApp() - app.run() - return 0 - except SystemExit as exc: - return int(getattr(exc, "code", 0) or 0) - - -def main(argv: Optional[List[str]] = None) -> int: - """Entry point for console_scripts. - - Accepts an optional argv list (useful for testing). Mode flags are parsed - and removed before dispatching to the selected runner. - """ - args = list(argv) if argv is not None else list(sys.argv[1:]) - - try: - mode, clean_args = _parse_mode_and_strip_args(args) - except ValueError as exc: - print(f"Error parsing mode flags: {exc}", file=sys.stderr) - return 2 - - # Early environment sanity check to detect urllib3/urllib3-future conflicts. - # When a broken urllib3 is detected we print an actionable message and - # exit early to avoid confusing import-time errors later during startup. - try: - from SYS.env_check import ensure_urllib3_ok - - try: - ensure_urllib3_ok(exit_on_error=True) - except SystemExit as exc: - # Bubble out the exit code as the CLI return value for clearer - # behavior in shell sessions and scripts. - return int(getattr(exc, "code", 2) or 2) - except Exception: - # If the sanity check itself cannot be imported or run, don't block - # startup; we'll continue and let normal import errors surface. - pass - - # If GUI requested, delegate directly (GUI may decide to honor any args itself) - if mode == "gui": - return _run_gui(clean_args) - - # Support quoting a pipeline (or even a single full command) on the command line. - # - # - If the user provides a single argument that contains a pipe character, - # treat it as a pipeline and rewrite the args to call the internal `pipeline` - # subcommand so existing CLI pipeline handling is used. - # - # - If the user provides a single argument that contains whitespace but no pipe, - # expand it into argv tokens (PowerShell commonly encourages quoting strings). - # - # Examples: - # mm "download-media | add-tag 'x' | add-file -store local" - # mm "download-media '' -query 'format:720p' -path 'C:\\out'" - if len(clean_args) == 1: - single = clean_args[0] - if "|" in single and not single.startswith("-"): - clean_args = ["pipeline", "--pipeline", single] - elif (not single.startswith("-")) and any(ch.isspace() for ch in single): - try: - expanded = shlex.split(single, posix=True) - if expanded: - clean_args = list(expanded) - except Exception: - pass - - # Default to CLI if --cli is requested or no explicit mode provided. - return _run_cli(clean_args) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000..03a9454 --- /dev/null +++ b/scripts/__init__.py @@ -0,0 +1 @@ +"""Minimal package marker for scripts-based entrypoints.""" \ No newline at end of file diff --git a/scripts/bootstrap.ps1 b/scripts/bootstrap.ps1 index cc4863f..b1f945a 100644 --- a/scripts/bootstrap.ps1 +++ b/scripts/bootstrap.ps1 @@ -226,8 +226,8 @@ if (-not $NoInstall) { # Verify top-level 'CLI' import and (if missing) attempt to make it available Write-Log "Verifying installed CLI import..." try { - & $venvPython -c "import importlib; importlib.import_module('medeia_macina.cli_entry')" 2>$null - if ($LASTEXITCODE -eq 0) { Write-Log "OK: 'medeia_macina.cli_entry' is importable in the venv." } + & $venvPython -c "import importlib; importlib.import_module('scripts.cli_entry')" 2>$null + if ($LASTEXITCODE -eq 0) { Write-Log "OK: 'scripts.cli_entry' is importable in the venv." } } catch {} try { @@ -493,7 +493,7 @@ if ($IsWindowsPlatform) { $target = $mmExe } else { $target = $venvPython - $args = "-m medeia_macina.cli_entry" + $args = "-m scripts.cli_entry" } if ($CreateDesktopShortcut) { $desk = [Environment]::GetFolderPath('Desktop') @@ -545,10 +545,10 @@ if ($env:MM_DEBUG) { if (Test-Path $py) { & $py -c "import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);" } else { python -c "import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);" } } -if (Test-Path $py) { & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE } +if (Test-Path $py) { & $py -m scripts.cli_entry @args; exit $LASTEXITCODE } if (Test-Path (Join-Path $repo 'CLI.py')) { & python (Join-Path $repo 'CLI.py') @args; exit $LASTEXITCODE } # fallback -python -m medeia_macina.cli_entry @args +python -m scripts.cli_entry @args '@ # Thin wrapper: prefer the canonical Python bootstrap installer $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path @@ -587,7 +587,7 @@ Write-Host 'Error: no suitable Python 3 interpreter found. Please install Python exit 127 # Inject the actual repo path safely (escape embedded double-quotes if any) $ps1Text = $ps1Text.Replace('__REPO__', $repo.Replace('"', '""')) # Ensure the PowerShell shim falls back to the correct module when the venv isn't present - # (No legacy 'medeia_entry' shim - use the packaged entry 'medeia_macina.cli_entry') + # (No legacy 'medeia_entry' shim - use the packaged entry 'scripts.cli_entry') if (Test-Path $mmPs1) { $bak = "$mmPs1.bak$(Get-Date -UFormat %s)" Move-Item -Path $mmPs1 -Destination $bak -Force @@ -626,7 +626,7 @@ Write-Host "" Write-Host "To run the app:" Write-Host " $ .\$VenvPath\Scripts\mm.exe (Windows) or" Write-Host " $ ./$VenvPath/bin/mm (Linux) or" -Write-Host " $ $venvPython -m medeia_macina.cli_entry" +Write-Host " $ $venvPython -m scripts.cli_entry" Write-Host "" Write-Host "If the global 'mm' launcher fails, collect runtime diagnostics by setting MM_DEBUG and re-running the command:"" if ($IsWindowsPlatform) { Write-Host " PowerShell: $env:MM_DEBUG = '1'; mm" } else { Write-Host " POSIX: MM_DEBUG=1 mm" } diff --git a/scripts/bootstrap.py b/scripts/bootstrap.py index 327e856..0d203f3 100644 --- a/scripts/bootstrap.py +++ b/scripts/bootstrap.py @@ -800,10 +800,10 @@ $venvScripts = Join-Path $venv 'Scripts' if (Test-Path $venvScripts) { $env:PATH = $venvScripts + ';' + $env:PATH } $py = Join-Path $venv 'Scripts\python.exe' $cli = Join-Path $repo 'CLI.py' -if (Test-Path $py) { & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE } +if (Test-Path $py) { & $py -m scripts.cli_entry @args; exit $LASTEXITCODE } if (Test-Path $cli) { & python $cli @args; exit $LASTEXITCODE } # fallback -python -m medeia_macina.cli_entry @args +python -m scripts.cli_entry @args """ try: ps1.write_text(ps1_text, encoding="utf-8") @@ -836,9 +836,9 @@ python -m medeia_macina.cli_entry @args ' Write-Host "MM_DEBUG: diagnostics" -ForegroundColor Yellow\n' " & $py -c \"import sys,importlib,importlib.util,traceback; print('sys.executable:', sys.executable); print('sys.path (first 8):', sys.path[:8]);\"\n" " }\n" - " & $py -m medeia_macina.cli_entry @args; exit $LASTEXITCODE\n" + " & $py -m scripts.cli_entry @args; exit $LASTEXITCODE\n" "}\n" - "python -m medeia_macina.cli_entry @args\n" + "python -m scripts.cli_entry @args\n" ) if mm_ps1.exists(): bak = mm_ps1.with_suffix(f".bak{int(time.time())}") @@ -916,7 +916,7 @@ python -m medeia_macina.cli_entry @args ' for pycmd in "$VENV/bin/python3" "$VENV/bin/python" "$(command -v python3 2>/dev/null)" "$(command -v python 2>/dev/null)"; do\n' ' if [ -n "$pycmd" ] && [ -x "$pycmd" ]; then\n' ' echo "---- Testing with: $pycmd ----" >&2\n' - " $pycmd - <<'PY'\nimport sys, importlib, traceback, importlib.util\nprint('sys.executable:', sys.executable)\nprint('sys.path (first 8):', sys.path[:8])\nfor mod in ('CLI','medeia_macina','medeia_macina.cli_entry'):\n try:\n spec = importlib.util.find_spec(mod)\n print(mod, 'spec:', spec)\n if spec:\n m = importlib.import_module(mod)\n print(mod, 'loaded at', getattr(m, '__file__', None))\n except Exception:\n print(mod, 'import failed')\n traceback.print_exc()\nPY\n" + " $pycmd - <<'PY'\nimport sys, importlib, traceback, importlib.util\nprint('sys.executable:', sys.executable)\nprint('sys.path (first 8):', sys.path[:8])\nfor mod in ('CLI','medeia_macina','scripts.cli_entry'):\n try:\n spec = importlib.util.find_spec(mod)\n print(mod, 'spec:', spec)\n if spec:\n m = importlib.import_module(mod)\n print(mod, 'loaded at', getattr(m, '__file__', None))\n except Exception:\n print(mod, 'import failed')\n traceback.print_exc()\nPY\n" " fi\n" " done\n" ' echo "MM_DEBUG: end diagnostics" >&2\n' @@ -927,18 +927,18 @@ python -m medeia_macina.cli_entry @args "fi\n" "# Prefer venv's python3, then venv's python\n" 'if [ -x "$VENV/bin/python3" ]; then\n' - ' exec "$VENV/bin/python3" -m medeia_macina.cli_entry "$@"\n' + ' exec "$VENV/bin/python3" -m scripts.cli_entry "$@"\n' "fi\n" 'if [ -x "$VENV/bin/python" ]; then\n' - ' exec "$VENV/bin/python" -m medeia_macina.cli_entry "$@"\n' + ' exec "$VENV/bin/python" -m scripts.cli_entry "$@"\n' "fi\n" "# Fallback to system python3, then system python (only if it's Python 3)\n" "if command -v python3 >/dev/null 2>&1; then\n" - ' exec python3 -m medeia_macina.cli_entry "$@"\n' + ' exec python3 -m scripts.cli_entry "$@"\n' "fi\n" "if command -v python >/dev/null 2>&1; then\n" " if python -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)'; then\n" - ' exec python -m medeia_macina.cli_entry "$@"\n' + ' exec python -m scripts.cli_entry "$@"\n' " fi\n" "fi\n" "echo 'Error: no suitable Python 3 interpreter found. Please install Python 3 or use the venv.' >&2\n" diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index 2a3f19a..f864abe 100644 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -332,8 +332,8 @@ if [[ "$NOINSTALL" != "true" ]]; then # If not explicitly requested, auto-selec # Verify the installed CLI module can be imported. This helps catch packaging # or installation problems early (e.g., missing modules or mispackaged project). echo "Verifying installed CLI import..." - if "$VENV_PY" -c 'import importlib; importlib.import_module("medeia_macina.cli_entry")' >/dev/null 2>&1; then - echo "OK: 'medeia_macina.cli_entry' is importable in the venv." + if "$VENV_PY" -c 'import importlib; importlib.import_module("scripts.cli_entry")' >/dev/null 2>&1; then + echo "OK: 'scripts.cli_entry' is importable in the venv." # Ensure the top-level 'CLI' module is importable (required by legacy entrypoints). # For PEP 660 editable installs, create a small .pth in the venv site-packages @@ -372,10 +372,10 @@ PY # Also verify we can run the packaged entrypoint using module form. If this fails # it suggests site-packages/.pth wasn't processed reliably by the interpreter. - if "$VENV_PY" -m medeia_macina.cli_entry --help >/dev/null 2>&1; then - echo "OK: 'python -m medeia_macina.cli_entry' runs in the venv." >&2 + if "$VENV_PY" -m scripts.cli_entry --help >/dev/null 2>&1; then + echo "OK: 'python -m scripts.cli_entry' runs in the venv." >&2 else - echo "ERROR: 'python -m medeia_macina.cli_entry' failed in the venv despite .pth being written; aborting." >&2 + echo "ERROR: 'python -m scripts.cli_entry' failed in the venv despite .pth being written; aborting." >&2 exit 6 fi else @@ -385,7 +385,7 @@ PY fi else - echo "WARNING: Could not import 'medeia_macina.cli_entry' from the venv." >&2 + echo "WARNING: Could not import 'scripts.cli_entry' from the venv." >&2 echo "Action: Try running: $VENV_PY -m pip install -e \"$REPO/scripts\" or inspect the venv site-packages to verify the installation." >&2 fi @@ -532,7 +532,7 @@ if [[ "$DESKTOP" == "true" ]]; then EXEC_PATH="$VENV_PATH/bin/mm" if [[ ! -x "$EXEC_PATH" ]]; then # fallback to python -m - EXEC_PATH="$VENV_PY -m medeia_macina.cli_entry" + EXEC_PATH="$VENV_PY -m scripts.cli_entry" fi APPDIR="$HOME/.local/share/applications" @@ -628,7 +628,7 @@ if [ -n "${MM_DEBUG:-}" ]; then import sys, importlib, traceback, importlib.util print('sys.executable:', sys.executable) print('sys.path (first 8):', sys.path[:8]) -for mod in ('CLI','medeia_macina','medeia_macina.cli_entry'): +for mod in ('CLI','medeia_macina','scripts.cli_entry'): try: spec = importlib.util.find_spec(mod) print(mod, 'spec:', spec) @@ -646,10 +646,10 @@ fi # Prefer venv's python3, then venv's python (module invocation - more deterministic) if [ -x "$VENV/bin/python3" ]; then - exec "$VENV/bin/python3" -m medeia_macina.cli_entry "$@" + exec "$VENV/bin/python3" -m scripts.cli_entry "$@" fi if [ -x "$VENV/bin/python" ]; then - exec "$VENV/bin/python" -m medeia_macina.cli_entry "$@" + exec "$VENV/bin/python" -m scripts.cli_entry "$@" fi # Fallback: packaged console script in the venv (older pip-generated wrapper) @@ -659,11 +659,11 @@ fi # Fallback to system python3, then system python (only if it's Python 3) if command -v python3 >/dev/null 2>&1; then - exec python3 -m medeia_macina.cli_entry "$@" + exec python3 -m scripts.cli_entry "$@" fi if command -v python >/dev/null 2>&1; then if python -c 'import sys; sys.exit(0 if sys.version_info[0] >= 3 else 1)'; then - exec python -m medeia_macina.cli_entry "$@" + exec python -m scripts.cli_entry "$@" fi fi @@ -756,7 +756,7 @@ To activate the virtualenv: source $VENV_PATH/bin/activate To run the app: $VENV_PATH/bin/mm # if installed as a console script - $VENV_PY -m medeia_macina.cli_entry # alternative + $VENV_PY -m scripts.cli_entry # alternative Global launcher installed: $USER_BIN/mm diff --git a/scripts/cli_entry.py b/scripts/cli_entry.py new file mode 100644 index 0000000000000000000000000000000000000000..04ae5720bcdf3882b1c1014d78cc8d4b2f9911ef GIT binary patch literal 20210 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