#+TITLE: Importing a file as module I want to import a file (maybe generated at run-time, maybe even the name only known at run-time) that does not exist in =sys.path=. In a couple of previous jobs that has happened with Python bindings generated from protocol descriptions. In the past I've done that by temporarily adding the parent to =sys.path= then running =importlib.import_module=, while being annoyed there is no function =import_file(name: str, file_path: Path) -> ModuleType=. It turns out that [[https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly][importlib.util]] has all the bits necessary, it's just a matter of putting them together: #+BEGIN_SRC python import importlib.util import os import sys from types import ModuleType def import_file(module_name: str, file_path: os.PathLike) -> ModuleType: spec = importlib.util.spec_from_file_location(module_name, file_path) # spec: ModuleSpec(name='bar', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7f30b1d10310>, origin='/tmp/tmp.WpcbUEkm32/foo.py') # if spec_from_file_location fails, it returns None rather than raise an exception if spec is None: raise ModuleNotFoundError(f"Can't import {module_name} from {file_path}") if spec.loader is None: raise ModuleNotFoundError(f"Can't import {module_name} from {file_path}") module = importlib.util.module_from_spec(spec) sys.modules[module_name] = module spec.loader.exec_module(module) return module #+END_SRC I suppose it is optional to add the module to =sys.modules=. If we do, that will allow future =import module_name= to find the same module (which might even be useful for the purpose of injecting different code into a 3rd party library...). If we don't, then we get a new copy of the module every time we import it again. Eg. if =/tmp/tmp.WpcbUEkm32/foo.py= defined a global: #+BEGIN_SRC python MODULE_GOBAL = 1 def myname(): return __name__ #+END_SRC And I try to imort it twice under the same name, same path, the module globals are disconnected: #+BEGIN_SRC python foo1 = import_file("foo", "/tmp/tmp.WpcbUEkm32/foo.py") foo2 = import_file("foo", "/tmp/tmp.WpcbUEkm32/foo.py") foo1.myname() # 'foo' foo2.myname() # 'foo' foo1.MODULE_GOBAL += 1 print(foo1.MODULE_GOBAL) # 2, as expected print(foo2.MODULE_GOBAL) # 1, unmodified #+END_SRC