2

Say I've got a arbitrarily big, modular Python 2.7 codebase:

project
↳ docs
↳ etc
↳ package
  ↳ module1
    ↳ submodule1
      ↳ subsubmodule1
        ↳ __init__.py
      ↳ subsubmodule2 (... and so on)
      ↳ __init__.py
    ↳ submodule2
      ↳ subsubmodule1
        ↳ __init__.py
      ↳ subsubmodule2 (... and so on)
      ↳ __init__.py
    ↳ submodule3 (... and so on)
      ↳ __init__.py
  ↳ module2
    ↳ submodule1
      ↳ __init__.py
    ↳ submodule2 (... and so on)
      ↳ __init__.py
    ↳ __init__.py        
  ↳ module3 (... and so on)
    ↳ __init__.py
  ↳ __init__.py
↳ test
  • project is the root folder - it's a PyCharm project and it's not a module.
  • project\package is the root Python module for the project. It contains many subdirectories, each of which is a Python module named moduleN.
  • Each project\package\moduleN module contains many subdirectories, each of which is a Python module named submoduleN ... and so on and so forth.

Say I have a particular Python script called foo.py that I would like to run, and it's located within one of the infinitely many submodules under package:

# foo.py:

from package.module2.submodule3 import foo
print foo.bar()

When the script is ran from PyCharm with Ctrl+F9: no problem, foo.bar() prints.

But when the script is ran from the Git Bash terminal, from the home directory, with:

python path/to/project/package/module4/submodule6/subsubmobile5/foo.py

The following error is thrown:

ImportError: No module named package.module2.submodule3


I would like to know what I need to do in order to get my script to run on Git Bash, and why there is a discrepancy in the first place between PyCharm and Git Bash. Is it something to do with PYTHONPATH?


EDIT:

  • Many StackOverflow answers suggest using some iteration of sys.path.append() hacks. None of these suggestions work in my case.
  • I set up a .bashrc file with export PYTHONPATH=absolute/path/to/project/package, where package is my source root in PyCharm, but the import error is still thrown. Relative paths do not work either. I have verified that the paths are correct with echo $PYTHONPATH. export PYTHONPATH=absolute/path/to/project similarly does not work.
  • The working directory doesn't seem to matter - Git Bash fails every time regardless of the working directory and PyCharm works every time regardless of the working directory.

EDIT 2:

The issue is still unresolved but it may have to do with PYTHONPATH not being set correctly on Git Bash. When Add Content Roots to PYTHONPATH is unticked, PyCharm throws the same import error as Git Bash.

alex
  • 6,818
  • 9
  • 52
  • 103
  • If you have your 'sources root' variable set in PyCharm, this will update your pythonpath variable.. this wont be automatically set in gitbash - you can right click a folder in your project structure and chooose 'mark directory as>' to see if it's marked as 'sources root'. Look for the folder icon in your project structure that's set as blue and then in your entry script to your program, set this directory on the python path using `os.path.append('dir')` – Alan Kavanagh Oct 11 '17 at 12:24
  • `project` is already set as the sources root in PyCharm. PyCharm works, my big problem is Git Bash. How do I "set this directory on the python path" in Git Bash? – alex Oct 11 '17 at 12:30
  • You have many options for setting the python path. You can update the .bashrc with the python path, you could update your python script to set the python path via os.path.append('//'), you could use a bash wrapper script for your python module to export the PYTHONPATH before it executes the Python module – Alan Kavanagh Oct 11 '17 at 12:32
  • Check out [this](https://stackoverflow.com/a/45711134/4889267) answer for a somewhat 'clean' solution to this problem – Alan Kavanagh Oct 11 '17 at 12:33
  • @AK47 I created a `.bashrc` profile with `export PYTHONPATH=path/to/project/package`, where `package` is my sources root in PyCharm, but I still get the import error. – alex Oct 11 '17 at 12:36
  • Is the path you added relative or absolute? If it's relative, are you starting the pyfile from a location that the relative path will point to the proper module? If it's absolute, are you sure that path will be the same for all future installations? Ivan's answer should be the right approach: add an absolute/relative path based on your current location on run directly in your pyfile. – BoboDarph Oct 11 '17 at 12:39
  • @BoboDarph To what - my `.bashrc` PYTHONPATH variable? It's absolute and relative to my home directory. – alex Oct 11 '17 at 12:40
  • A path cannot be both absolute and relative at the same time. Absolute in linux = starts with a "/". Also note that after updating .bashrc, you need to reload your shell for the changes to take effect (AFAIK). – BoboDarph Oct 11 '17 at 12:42
  • @BoboDarph Yeah good point, it was relative to my home directory. This didn't work and absolute (relative to the root directory) does not work either. – alex Oct 11 '17 at 12:45
  • Check it got properly set by echoing it. echo $PYTHONPATH – BoboDarph Oct 11 '17 at 12:47
  • @BoboDarph I did - no problems here. – alex Oct 11 '17 at 12:48
  • Then the path is most likely incorrect, as the python interpretor cannot find the module. – BoboDarph Oct 11 '17 at 12:50
  • @BoboDarph No: the path is absolutely correct. – alex Oct 11 '17 at 12:51
  • @BoboDarph I should mention that I added `os.environ['PYTHONPATH'].split(os.pathsep)` to the top of my script to confirm - *PyCharm* shows the correct absolute path to the package as specified in `.bashrc`, whereas *Git Bash* does not - it just displays `C:/` (for instance). Even though `echo $PYTHONPATH` on Git Bash returns the correct path. – alex Oct 11 '17 at 14:04
  • Err, if you're running on windows, editing the .bashrc of git bash will do zilch for your command line (cmd.exe). Appending a path to a system variable in windows is done differently. Pycharm injects it probably in the environment it's running. Just google "add to pythonpath windows". – BoboDarph Oct 13 '17 at 14:40
  • @BoboDarph I'm running `git-bash.exe`, not `cmd.exe`. (Does `git-bash.exe` leverage `cmd.exe` under the hood?) In any case, I declared `PYTHONPATH` as a user environmental variable and it still throws the same import error - the point you made is good but it's one of the first things I tried. – alex Oct 13 '17 at 16:50
  • Is the working directory the same on PyCharm and on Bash? – Javier Oct 16 '17 at 03:57
  • @Javier If you mean the location of the `.py` file I am trying to run, it's the same for both PyCharm and Bash. If you mean the executables that run PyCharm and Bash respectively - no. – alex Oct 16 '17 at 18:28
  • I mean the current working directory. I find that this is a common source of confusion for my colleges. See this: http://www.linfo.org/current_directory.html – Javier Oct 16 '17 at 22:05
  • @Javier Sorry yeah - in each case I am calling `python path/to/project/package/module4/submodule6/subsubmobile5/foo.py` from my home directory. My project is located in another directory altogether. – alex Oct 17 '17 at 11:38
  • @alex PyCharm surely is using the project's directory as working directory. You can see this in the configuration of the "run". – Javier Oct 17 '17 at 23:53
  • The Python path must be `PYTHONPATH=absolute/path/to/project/`(but not `PYTHONPATH=absolute/path/to/project/package`) – Laurent LAPORTE Oct 18 '17 at 09:40
  • @LaurentLAPORTE Tried both (edited `PYTHONPATH` via Git Bash `.bashrc` *and* my Windows user environmental variables) but still no luck. – alex Oct 18 '17 at 12:35
  • @Javier Let me try to clear up the confusion. PyCharm works regardless of whether or not the working directory is `H:/path/to/project/package/module4/submodule6/subsubmobile5`or `H:/`. I changed this (and applied the changes) via `Run > Edit Configurations`. Git Bash does not run the script from any working directory - it throws the same error all the time. I am using `alias call_the_script='python /h/absolute/path/to/project/package/module4/submodule6/subsubmobile5/foo.py'`. – alex Oct 18 '17 at 12:40
  • @Javier I *did* however find out that in PyCharm, under `Run > Edit Configurations`, when `Add Content Roots to PYTHONPATH` is *unticked*, it throws the same import error as Git Bash - so I believe I'm on the right track thinking that the issue is with `PYTHONPATH`. I'll update my question to reflect this. – alex Oct 18 '17 at 13:18

3 Answers3

3

The right way to solve your problem is not to hack the PYTHONPATH, because this won't be useful if your library/application has third-party dependencies.

To work properly, PyCharm used two things:

  • the Source Root (which is added to the PYTHONPATH), and,
  • the Project Interpreter (i.e. the virtualenv),

To check that, open a Terminal view in PyCharm, and try:

$ python
>>> import sys
>>> for p in sys.path:
...     print(p)
...

The right way is using a virtualenv.

To create a virtualenv on Windows, go to a directory where you want to create a virtualenv. It can be in a unique directory (like recommended by pew), or in your project directory (usually in .venv).

virtualenv -p C:\Python27\python.exe yourvenv

Then, activate your virtualenv and install your application in development/editable mode:

yourvenv\Scripts\activate

cd path\to\project\
pip install -e .

Here, you have installed your library/application with all its dependencies. The -e flag means "editable" (which match the old "develop" mode), See pip documentation for that.

Whenever you want to run a script, you can do as follow:

yourvenv\Scripts\activate
python -m package.module4.submodule6.subsubmobile5.foo

On Windows, you can also do:

yourvenv\Scripts\activate
python path\to\project\package\module4\submodule6\subsubmobile5\foo.py

On Git bash, you do:

source yourvenv/Scripts/activate
python path/to/project/package/module4/submodule6/subsubmobile5/foo.py

If you want to call your Python script from another batch, you can do:

yourvenv/Scripts/python.exe -m package.module4.submodule6.subsubmobile5.foo
yourvenv/Scripts/python.exe path/to/project/package/module4/submodule6/subsubmobile5/foo.py
Laurent LAPORTE
  • 21,958
  • 6
  • 58
  • 103
  • Thank you, but pip is a no-go in my particular case. I am not currently referencing any third-party libraries from my program and I should note that I cannot use a third-party library to solve this issue. – alex Oct 16 '17 at 18:29
1

Notice: the solution exposed below won't work if your application/library has third-party dependencies. For that, you need a virtualenv. See my previous answer.

You have the following tree structure in path\to\project:

path\to\project
├───docs
├───etc
├───package
│   │   __init__.py
│   ├───module1 [...]
│   ├───module2
│   │   │   __init__.py
│   │   ├───submodule1 [...]
│   │   ├───submodule2 [...]
│   │   └───submodule3
│   │           foo.py
│   │           __init__.py
│   ├───module3 [...]
│   └───module4
│       │   __init__.py
│       └───submodule6
│           │   __init__.py
│           └───subsubmobile5
│                   foo.py
│                   __init__.py
└───tests

Here, package is the root Python package of your project, and path\to\project is your project source directory (the one that PyCharm use).

To run a script in this packages, for instance the script package/module4/submodule6/subsubmobile5/foo.py, you need to set the PYTHONPATH to the root of the project, i.e. path\to\project.

But, since you are on Windows and run Git Bash, you need to convert the Windows path to a valid Linux path, with forward slash.

One way to do that is as follow:

$ export PYTHONPATH=path/to/project && python path/to/project/package/module4/submodule6/subsubmobile5/foo.py

You can even execute the module package.module4.submodule6.subsubmobile5.foo:

$ export PYTHONPATH=path/to/project && python -m package.module4.submodule6.subsubmobile5.foo
Laurent LAPORTE
  • 21,958
  • 6
  • 58
  • 103
  • When I `cd` to the `project` directory, running `python -m package.module4.submodule6.subsubmobile5.foo` throws the error `/usr/bin/python2.7: Import by filename is not supported.`. Running `python -m package.module4.submodule6.subsubmobile5` throws the error `/usr/bin/python2.7: No module named package.module4.submodule6.subsubmobile5`. Changing the *Windows* `PYTHONPATH` under User Environmental Variables appears to have no impact as Git Bash does not appear to inherit its `PYTHONPATH`. Also my Git Bash `PYTHONPATH` has been using forward slashes this entire time. – alex Oct 18 '17 at 13:11
1

Ultimately, Laurent's answers helped me figure this out.

I used:

    import sys
    for p in sys.path:
        print(p)

to help me determine that there was indeed a discrepancy between what's being added to PYTHONPATH on PyCharm versus Git Bash. On PyCharm, both /path/to/project and /path/to/project/package are being added to PYTHONPATH. On Git Bash, only one is being added.

So I modified my export statement in .bashrc such that it appends both paths to PYTHONPATH; i.e. from:

export PYTHONPATH="${PYTHONPATH}:absolute/path/to/project/package"

to:

export PYTHONPATH="${PYTHONPATH}:absolute/path/to/project:absolute/path/to/project/package"

The import error was then resolved.1 Similarly, the script works as-intended on the Windows command prompt when the PATH AND PYTHONPATH user environmental variables are similarly declared.

My best guess is that setting the Sources Root in PyCharm to something other than the project directory may require multiple additions to PYTHONPATH.

1 On a site note: my script then started hanging indefinitely which was resolved by way of aliasing Python: alias python="winpty python".

EDIT:

Git Bash operates under MinGW64 - where lists of Windows paths in export statements are subject to Posix path conversion prior to being processed by Python's import resolution.

So technically, export PYTHONPATH="absolute/path/to/project;absolute/path/to/project/package" should correctly resolve to two paths.

alex
  • 6,818
  • 9
  • 52
  • 103
  • You must have a problem in your source code. Search in your source code (use a recursive `grep -r import`) the imports that don't start with your root package. You can put the result in a GitHub Gist and share it. I'll take a look. – Laurent LAPORTE Oct 18 '17 at 21:05
  • @LaurentLAPORTE Originally my `from blah import blah` statements started at the project level (i.e. `from project.package.module1....`) but I felt that `project.` was getting redundant so I nixed it in favor of `package.module1.`. I don't really consider that a problem (unless it is!). Otherwise this convention is consistent throughout the application. – alex Oct 19 '17 at 12:53
  • The root package is `package`. To fix your problem, replace all `project.package.xxx` by `package.xxx`. You point the error, great work! – Laurent LAPORTE Oct 19 '17 at 16:00
  • @LaurentLAPORTE Thanks Laurent - I should note that the convention throughout the project *is* `package.module1...`. – alex Oct 19 '17 at 16:36