mitogen icon indicating copy to clipboard operation
mitogen copied to clipboard

`No module named my.custom.module` when trying to use `call_async()` from a Python package

Open mrled opened this issue 1 year ago • 0 comments

I can't use mitogen to run functions in my code on remote nodes, even though they do run functions out of the Python standard library just fine. My code is not just a single file script, it's a Python package installable from pip. Is running mitogen from a Python package supported? Is there another way to accomplish what I want to do?

Info about my use case:

  • Not using Ansible
  • Building a command-line tool that sometimes has to ssh to remote machines
  • My tool is built as a Python package with multiple files and sub-packages; it's not a single script
  • I'm using the tip of the master branch of Mitogen, which reports its __version__ attribute as (0, 3, 4, 'dev0')
  • I'm running my code inside Linux (an Alpine Docker container on macOS) running Python 3.10.5
  • Remote nodes are running Alpine Linux running Python 3.10.4

When I try to pass one of my own functions to call_async(), it logs a DEBUG message that says progfiguration.remoting is submodule of a locally loaded package, and terminates with an error No module named 'progfiguration.remoting', where progfiguration.remoting is the name of the Python package containing my code. However, passing a function from the Python standard library works just fine.

Here's what my code looks like:

def mitogen_example_remote_function():
    return "I am an example function intended to be run on remote hosts by mitogen"


def mitogen_example():
    hostnames = [
        "jesseta.home.micahrl.com",
        "kenasus.home.micahrl.com",
        "zalas.home.micahrl.com",
    ]
    broker = Broker()
    router = Router(broker)

    try:
        # hostname:context pairs
        contexts = {n: router.ssh(hostname=n, username="root", python_path="python3") for n in hostnames}
        # context_id:hostname pairs
        # Used to get the hostname for a given response
        hostname_by_context_id = {context.context_id: hostname for hostname, context in contexts.items()}

        # This doesn't work at all, showing log lines including
        #   [2022-10-05 17:34:28,245] [mitogen.importer.[ssh.kenasus.home.micahrl.com]] [DEBUG] progfiguration.remoting is submodule of a locally loaded package
        # and
        #   mitogen.core.CallError: builtins.ModuleNotFoundError: No module named 'progfiguration.remoting'
        # (progfiguration.remoting is the name of this module)
        print("Calling a function defined in this Python module")
        calls = [ctx.call_async(mitogen_example_remote_function) for ctx in contexts.values()]
        for msg in Select(calls):
            hostname = hostname_by_context_id[msg.src_id]
            result = msg.unpickle()
            print(f"{hostname}: {result}")

    finally:
        broker.shutdown()

If instead of calling my example remote function, I call something from the Python standard library, that does work.

        print("Calling a function that's part of the Python standard library which returns a string")
        calls = [ctx.call_async(socket.gethostname) for ctx in contexts.values()]
        for msg in Select(calls):
            hostname = hostname_by_context_id[msg.src_id]
            result = msg.unpickle()
            for line in result.split("\n"):
                print(f"{hostname}: {line}")
            # This works just fine, returns lines like
            #   jesseta.home.micahrl.com: jesseta

mrled avatar Oct 05 '22 23:10 mrled