30

I have a project that is structured as follows:

project
├── api
│   ├── __init__.py
│   └── api.py
├── instance
│   ├── __init__.py
│   └── config.py
├── package
│   ├── __init__.py
│   └── app.py
├── requirements.txt
└── tests
    └── __init__.py

I am trying to call the config.py file from the package/app.py as shown below:

# package/app.py
from instance import config

# I've also tried
import instance.config
import ..instance.config
from ..instance import config

But I always get the following error:

Traceback (most recent call last):
  File "/home/csymvoul/projects/project/package/app.py", line 1, in <module>
    from instance import config
ModuleNotFoundError: No module named 'instance'

Modifying the sys.path is not something I want to do. I know that this question is very much answered but the answers that were given, did not work for me.

EDIT: When moving the app.py to the root folder it works just fine. But I need to have it under the package folder.

techPirate99
  • 146
  • 1
  • 9
csymvoul
  • 677
  • 3
  • 15
  • 30
  • I've also checked this https://stackoverflow.com/questions/6670275/python-imports-for-tests-using-nose-what-is-best-practice-for-imports-of-modul but it did not seem to work – csymvoul Apr 15 '20 at 17:13

2 Answers2

27

You can add the parent directory to PYTHONPATH, in order to achieve that, you can use OS depending path in the "module search path" which is listed in sys.path. So you can easily add the parent directory like following:

import sys
sys.path.insert(0, '..')

from instance import config

Note that the previous code uses a relative path, so you must launch the file inside the same location or it will likely not work. To launch from anywhere, you can use the pathlib module.

from pathlib import Path
import sys
path = str(Path(Path(__file__).parent.absolute()).parent.absolute())
sys.path.insert(0, path)

from instance import config

However, the previous approach is more a hack than anything, in order to do things right, you'll first need to reshape your project structure according to this very detailed blog post python packaging, going for the recommended way with a src folder.

  • Your directory layout must look like this:
project
├── CHANGELOG.rst
├── README.rst
├── requirements.txt
├── setup.py
├── src
│   ├── api
│   │   ├── api.py
│   │   └── __init__.py
│   ├── instance
│   │   ├── config.py
│   │   └── __init__.py
│   └── package
│       ├── app.py
│       └── __init__.py
└── tests
    └── __init__.py

Note that you don't really need the requirements.txt because you can declare the dependencies inside your setup.py. A sample setup.py (adapted from here):

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function

import io
import re
from glob import glob
from os.path import basename
from os.path import dirname
from os.path import join
from os.path import splitext

from setuptools import find_packages
from setuptools import setup


def read(*names, **kwargs):
    with io.open(
        join(dirname(__file__), *names),
        encoding=kwargs.get('encoding', 'utf8')
    ) as fh:
        return fh.read()


setup(
    name='nameless',
    version='1.644.11',
    license='BSD-2-Clause',
    description='An example package. Generated with cookiecutter-pylibrary.',
    author='mpr',
    author_email='contact@ionelmc.ro',
    packages=find_packages('src'),
    package_dir={'': 'src'},
    include_package_data=True,
    zip_safe=False,
    classifiers=[
        # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Operating System :: Unix',
        'Operating System :: POSIX',
        'Operating System :: Microsoft :: Windows',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: Implementation :: CPython',
        'Programming Language :: Python :: Implementation :: PyPy',
        # uncomment if you test on these interpreters:
        # 'Programming Language :: Python :: Implementation :: IronPython',
        # 'Programming Language :: Python :: Implementation :: Jython',
        # 'Programming Language :: Python :: Implementation :: Stackless',
        'Topic :: Utilities',
    ],
    keywords=[
        # eg: 'keyword1', 'keyword2', 'keyword3',
    ],
    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
    install_requires=[
        # eg: 'aspectlib==1.1.1', 'six>=1.7',
    ],
    extras_require={
        # eg:
        #   'rst': ['docutils>=0.11'],
        #   ':python_version=="2.6"': ['argparse'],
    },
    setup_requires=[
        # 'pytest-runner',
    ],
    entry_points={
        'console_scripts': [
            'api = api.api:main',
        ]
    },
)

The content of my api.py:

from instance import config

def main():
    print("imported")
    config.config()

The content of my config.py:

def config():
    print("config imported successfully")

You can find all the previous here

  • Optional but recommended: create a virtual environment, I use venv (Python 3.3 <=) for that, inside the root of the project:
python -m venv .

And to activate:

source bin/activate
  • Now I can install the package:

Using pip install -e . (with the dot) command inside the root of the project

  • Your import from instance import config works now, to confirm you can run api.py with:
python src/api/api.py
RMPR
  • 3,368
  • 4
  • 19
  • 31
  • the second option works just fine, but isn't there a simpler way to achieve that without messing with the path? – csymvoul Apr 15 '20 at 17:48
  • To use something simpler, you will need to adjust your directory structure, have a look at [python packaging](https://blog.ionelmc.ro/2014/05/25/python-packaging/) – RMPR Apr 15 '20 at 18:04
  • from what I undestand, when I add a `__init__.py` in a directory, python identifies it as a module. Why isn't this the case now? I am ok to adjust the project's structure but TBH I do not see a difference besides this `src` directory with my current structure. Unless you suggest of having them all under one folder – csymvoul Apr 15 '20 at 18:12
  • The structure starting with a src/ is aimed explicitly at not enabling you to put an `__init__.py` file in src/ folder. Answer updated. – RMPR Apr 15 '20 at 18:56
  • What you suggested does not seem to work for me. I get this error when I run the `pip install -e .` command: ```Obtaining file:///home/csymvoul/projects/project ERROR: Files/directories not found in /home/csymvoul/projects/project/```. Note that I am already using a virtual environment – csymvoul Apr 16 '20 at 07:20
  • Did you create a `setup.py` at the root of your project? – RMPR Apr 16 '20 at 08:51
  • Yes. And tried to `pip install e .` but this error came up – csymvoul Apr 20 '20 at 20:58
  • My script has more nested folders with different .py files. If I install and then run it I get errors like: File "cli.py", line 20, in from lib import utils ModuleNotFoundError: No module named 'lib'. One of my folders is called lib and indeed it contains utils.py, how can I fix this? – Pitto Aug 17 '21 at 09:58
  • What is your project structure? If the files are scattered here and there, you might need to put them as suggested in the answer and then follow through with the rest. – RMPR Aug 19 '21 at 22:09
1

In Python 3.3 onwards you don't need __init__.py files in your subdirectories for the purpose of imports. Having them can actually be misleading as it causes the creation of package namespaces in each folder containing an init file, as described here.

By removing all those __init__.py files you will be able to import files in the namespace package (including subdirectories) when running app.py, but that's still not want we want.

The Python interpreter still doesn't know how to reach your instance namespace. In order to do that you can use the PYTHONPATH environment variable, including a path that is parent to config.py. You can do that as suggested in @RMPR's answer with sys.path, or by directly setting the environment variable, for instance:

PYTHONPATH=/home/csymvoul/projects/project python3 /home/csymvoul/projects/project/package/app.py

Then importing the dependency like from instance import config.

stjernaluiht
  • 730
  • 6
  • 14
  • What you suggested with the `PYTHONPATH` works when I try to run in terminal. I know that this is an of topic question, but how can I add this `PYTHONPATH` to VSCode in order to consider the root folder as path? – csymvoul Apr 16 '20 at 07:17
  • You can add enviroment variables as configuration of your integrated terminal like this in your settings.json: "terminal.integrated.env.linux": { "PYTHONPATH": "${workspaceFolder}" } – stjernaluiht Apr 16 '20 at 10:29
  • The command "Preferences: Open workspace settings (JSON)" will take you directly to the settings file. Be mindful that this will only set the PYTHONPATH variable locally, you'll need to set it if you deploy the code in another environment. Here's a relevant [docs page](https://code.visualstudio.com/docs/python/environments#_use-of-the-pythonpath-variable) from VSCode. – stjernaluiht Apr 16 '20 at 10:35
  • In Python 3.3+ you still need to add `__init__.py` files to your project sub-directories unless you have a very specific use case, [as described here](https://stackoverflow.com/a/48804718/127427). This holds true today as of Python 3.10+. – ianyoung Mar 04 '22 at 09:35