cli: add headless command-line layer (bal_* commands for the daemon, cmdline entry point, manifest 'cmdline' support, offline controller tests)
This commit is contained in:
69
bal/cmdline.py
Normal file
69
bal/cmdline.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
bal.cmdline
|
||||
===========
|
||||
|
||||
Compatibility shim for Electrum's plugin loader (command-line front-end).
|
||||
|
||||
Electrum loads a plugin with ``gui_name='cmdline'`` by importing the
|
||||
``cmdline`` module of the plugin package and looking for a ``Plugin`` class.
|
||||
The real implementation lives in the ``bal.cli`` sub-package, so this module
|
||||
re-exports ``Plugin`` from ``bal.cli.plugin``.
|
||||
|
||||
Like ``qt.py``, this file is not a one-line relative import because the very
|
||||
same code may be loaded as an *external* plugin from a ``.zip``, where Electrum
|
||||
imports the package under the synthetic top-level name
|
||||
``electrum_external_plugins.bal`` and never registers the intermediate parent
|
||||
packages. See the module docstring of ``bal.qt`` for the full rationale. The
|
||||
shim resolves the run-time package name, backfills the missing parents into
|
||||
``sys.modules`` and imports the real implementation via
|
||||
:func:`importlib.import_module`.
|
||||
|
||||
Unlike ``qt.py``, this module MUST never import PyQt (the daemon loads it in a
|
||||
headless process).
|
||||
"""
|
||||
|
||||
import importlib
|
||||
import sys
|
||||
|
||||
|
||||
def _ensure_parent_packages(pkg_name: str) -> None:
|
||||
"""Make sure every ancestor package of *pkg_name* is in ``sys.modules``.
|
||||
|
||||
When loaded from a zip as an external plugin, Electrum only executes the
|
||||
plugin package ``__init__`` and the ``cmdline`` module. The synthetic root
|
||||
package (e.g. ``electrum_external_plugins``) and any intermediate packages
|
||||
may be missing from ``sys.modules``, which breaks relative/absolute
|
||||
sub-module imports. We backfill them here using this module's own loader
|
||||
so that ``importlib`` can find sibling sub-packages.
|
||||
"""
|
||||
parts = pkg_name.split(".")
|
||||
# Walk from the top-most ancestor down to (but not including) pkg_name.
|
||||
for i in range(1, len(parts)):
|
||||
ancestor = ".".join(parts[:i])
|
||||
if ancestor in sys.modules:
|
||||
continue
|
||||
try:
|
||||
importlib.import_module(ancestor)
|
||||
except Exception:
|
||||
# The synthetic root (e.g. 'electrum_external_plugins') often has no
|
||||
# real spec. Create a minimal namespace package stub so that the
|
||||
# import machinery can still resolve its children.
|
||||
import types
|
||||
|
||||
module = types.ModuleType(ancestor)
|
||||
module.__path__ = [] # mark as a (namespace) package
|
||||
sys.modules[ancestor] = module
|
||||
|
||||
|
||||
# The package this module belongs to. Could be 'electrum.plugins.bal' (internal)
|
||||
# or 'electrum_external_plugins.bal' (external zip), depending on how Electrum
|
||||
# loaded us.
|
||||
_PKG = __package__ or "bal"
|
||||
|
||||
_ensure_parent_packages(_PKG)
|
||||
|
||||
# Import the real implementation using the fully-qualified, run-time package
|
||||
# name so it works regardless of the synthetic prefix Electrum assigned.
|
||||
_plugin_module = importlib.import_module(_PKG + ".cli.plugin")
|
||||
|
||||
Plugin = _plugin_module.Plugin # noqa: F401 (re-exported for Electrum)
|
||||
Reference in New Issue
Block a user