Python Relative Imports in VSCode (Fix ModuleNotFoundError and Auto-completion)
Post
Cancel

# Python Relative Imports in VSCode (Fix ModuleNotFoundError and Auto-completion)

When you work on semi-complex Python projects, they are sometimes composed out of several smaller projects. For example, you or your colleagues developed a library or package of classes and functions you now want to use in your current project. One way of including a package (here my_package) into your current project is to copy it into your project folder (or have it as a git submodule). A project structure could look like this, where the package(s) are all included inside the libs folder:

 1 2 3 4 5 6 7 $workspaceFolder └── my_code ├── libs │ └── my_package │ ├── __init__.py │ └── classes.py └── main.py The classes.py may contain the following code (__init__.py is empty):  1 2 3 4 5 6 class MyClass: def __init__(self, name): self.name = name def __repr__(self): return self.name and main.py can access MyClass by importing it with its absolute path:  1 2 3 4 5 from libs.my_package.classes import MyClass if __name__ == "__main__": a = MyClass("a") print(a) However, sometimes your projects require that a libs directory is on the same folder level as the folder where your project can be found, or several projects share the same packages and they are stored outside of your workspace. Then the project structure could look like this:  1 2 3 4 5 6 7$workspaceFolder ├── libs │   └── my_package │   ├── __init__.py │   └── classes.py └── my_code └── main.py

Now main.py can’t access MyClass using the import statement from above because libs/my_package is not in the folder that main.py is in, and when running main.py, the following ModuleNotFoundError is raised:

 1 2 3 4 Traceback (most recent call last): File "$workspaceFolder\my_code\main.py", line 1, in from libs.my_package.classes import MyClass ModuleNotFoundError: No module named 'libs.my_package' There is a dirty fix to remove the ModuleNotFoundError by extending the PYTHONPATH inside of main.py. PYTHONPATH is an environment variable that holds paths to additional directories in which the Python interpreter will look into to find packages and modules. PYTHONPATH can be manually extended within a Python file using sys:  1 2 3 4 5 6 7 8 import sys sys.path.append("../libs") from my_package.classes import MyClass if __name__ == "__main__": a = MyClass("a") print(a) However, this solution not only looks terrible, but it also has a lousy code design. Imagine you got several Python files that want to access MyClass. In each of these files, you have to add the first two lines. When you now move libs somewhere else. Every file that imports MyClass has to be changed, which is tedious and error-prone. A better solution would be to have a single file to extend the PYTHONPATH. You can either extend PYTHONPATH systemwide by appending the full path to libs to it, whereas several paths are separated using a colon :. But then you have to tell everyone who uses your code to do this for their system. So the preferred solution is to ask VSCode to extend the PYTHONPATH only for your project which you can also add to your git repository such that others don’t have to extend their PYTHONPATH manually. First, you need to add a launch.json to your workspace that tells VSCode what and how to run your code. To create a launch.json, go to Run and Debug in the VSCode sidebar by clicking on the bug and run icon or pressing Ctrl+Shift+D. Then click on create launch.json file and choose Module, press Enter, and enter the path to the Python file you would like to run while folders a separated with a dot .. For the workspace in this example, you would enter my_code.main because main.py is inside my_code, which is the workspace’s root. Now VSCode added a .vscode directory to your workspace, and inside it, you can find a launch.json file. A launch.json allows you to run your code regardless of which files are currently opened or in focus. You can now run your code by pressing Ctrl+F5 or Cmd+F5. Inside the launch.json you have to add a new env segment that will tell VSCode to extend the PYTHONPATH before running your program:  1 2 3 4 5 6 7 8 9 10 11 12 { "version": "0.2.0", "configurations": [ { "name": "Python: Module", "type": "python", "request": "launch", "module": "my_code.main", "env": {"PYTHONPATH": "${workspaceFolder}/libs/"} } ] }

For this example, PYTHONPATH will be extended with ${workspaceFolder}/libs/.${workspaceFolder} is the variable that contains the path to the root folder of your current VSCode workspace, and as libs is a folder inside the root /libs/ is added. You can also use relative paths, including .. when libs is outside your workspace.

Now the sys.path.append can be removed from main.py and you can run main.py by pressing Ctrl+F5/Cmd+F5. But now VSCode complains that it can’t find my_package.classes and it won’t give you auto-completion for any of the classes and functions from my_package, and having not auto-completion almost defeats the whole purpose of having an editor or IDE.

However, you can get auto-completion back by adding a .vscode/settings.json to your workspace. To do so, open the command palette by pressing Ctrl+Shift+P on Windows and Linux or Cmd+Shift+P on macOS and enter settings.json and press Enter when Preferences: Open Workspace Settings (JSON) is selected.

This will create a settings.json within .vscode, and in it, you have to tell VScode where to look for additional packages by adding a python.analysis.extraPaths segment containing the path to libs:

 1 2 3 { "python.analysis.extraPaths": ["\${workspaceFolder}/libs/"] }

This will extend the PYTHONPATH for the VSCode code analysis and auto-completion, and VSCode won’t complain about an import error anymore: