Skip to content

Instantly share code, notes, and snippets.

@mportesdev
Last active December 11, 2023 03:29
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mportesdev/afb2ec26021ccabee0f67d6f7d18be3f to your computer and use it in GitHub Desktop.
Save mportesdev/afb2ec26021ccabee0f67d6f7d18be3f to your computer and use it in GitHub Desktop.
Importing Python source code from a script without the .py extension

Importing Python source code from a script without the .py extension

Generally, to import a python module programatically when you know the file's path, you could do something like this:

import importlib.util
import sys


def import_from_file(module_name, file_path):
    spec = importlib.util.spec_from_file_location(module_name, file_path)
    module = importlib.util.module_from_spec(spec)

    sys.modules[module_name] = module    # this step is not necessary, but it's
                                         # probably not a bad idea to have a manually
                                         # imported module stored in sys.modules
    spec.loader.exec_module(module)
    return module


foo = import_from_file('foo', 'path/to/foo.py')

However, if the file doesn't have a .py extension, Python won't know that your file contains a Python source code, and import will fail. The reason is that the file extensions recognized by the import machinery are more or less hard-coded in the source code of importlib. As a result, importlib won't know which loader to use for an unknown file type, and the spec_from_file_location method will return None.

A possible solution is to create a SourceFileLoader object and pass it as the loader parameter to the spec_from_file_location method:

from importlib.machinery import SourceFileLoader
...

def import_from_file(module_name, file_path):
    loader = SourceFileLoader(module_name, file_path)
    spec = importlib.util.spec_from_file_location(module_name, loader=loader)
    ...

Note however that you no longer need to pass file_path to spec_from_file_location, as this information is already stored in the loader object. Thanks to this, the function can be rewritten in a slightly more straightforward manner by replacing spec_from_file_location with spec_from_loader:

...

def import_from_file(module_name, file_path):
    loader = SourceFileLoader(module_name, file_path)
    spec = importlib.util.spec_from_loader(module_name, loader)
    ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment