14

Before I even start I want to say - I know, that there are like bazillion questions similar to this, but I couldn't find the answer to my problem. I have a dir structure like this:

.
├── project
│   ├── A
│   │   ├── __init__.py
│   │   └── somelib.py
│   ├── B
│   ├── C
│   │   └── C
│   │       ├── foo.py
│   │       └── __init__.py
│   └── __init__.py
└── run.sh

run.sh:

python3 project/C/C/foo.py

foo.py:

from project.A.somelib import somefunc


VS Code actually gets the intellisense in foo.py - it tells me what funcitons/variables I can import from somelib. But when I run run.sh, I get this error message:

from project.A.somelib import somefunc
ModuleNotFoundError: No module named 'project'

Is there a way to solve this while preserving this directory structure?


  • adding project/__init__.py changed nothing
  • the sys.path in foo.py looks like this:
['/home/dabljues/projects/project/project/C/C', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/usr/lib/python3.7/site-packages']

restrictions:

  • I can't modify neither sys.path in the files nor PYTHONPATH before running the scripts
  • I can't pip-install anything
  • I don't have sudo-access
  • I can't create a virtualenv, since the scripts are supposed to be downloadable and quickly executable
Community
  • 1
  • 1
dabljues
  • 1,663
  • 3
  • 14
  • 30
  • can you add a project/__init__.py? – Anthony Kong Aug 20 '19 at 09:35
  • was one of the similar questions this one? https://stackoverflow.com/questions/50155464/using-pytest-with-a-src-layer It runs a unit test instead of a script, but the problem might be the same - you need to either make `project` installable and install it, or add the project root in some other way to the `PYTHONPATH`. – Arne Aug 20 '19 at 10:02
  • Can you add the line `import sys; print(sys.path)` at the top of `run.sh` and include the output in your question? – Arne Aug 20 '19 at 10:03
  • Can you create a [virtual environment](https://virtualenv.pypa.io/en/latest/) that is owned by your current user? – Arne Aug 20 '19 at 11:31
  • I could, but it's a script which will be downloaded many times from repo, it should be fast and I was told that it wouldn't have time to create venv – dabljues Aug 20 '19 at 11:52
  • 1
    Fair enough. With that many restrictions in place a good solution might not be feasible.. I still think that the best way would be to turn everything that is not a script into an installable package and ask your users to install it. Then all the scripts would have a common well defined entrypoint. Also @dabljues, I reformatted your question a little, does it still reflect your original question? – Arne Aug 20 '19 at 12:15
  • Yes it does, I guess. Anyway, I am accepting your answer as it actually really good, if someone isn't as restricted as I am – dabljues Aug 20 '19 at 12:58
  • Thanks, I appreciate it. Good luck with the project! – Arne Aug 20 '19 at 13:05

4 Answers4

11

IDEs like VSCode or Pycharm make their own assumptions about a project, and will usually correctly link modules even if the interpreter that will ultimately run the code can't.

The reason why project.A.somelib can't be found is visible in your sys.path output, which gives you the places where python will search for modules. Since '/home/dabljues/projects/project/project' is not included, there is no way for python to resolve it during runtime.


A quick hack

You can just add the path manually to sys.path, either in the source file by running import sys; sys.insert(0, '/home/dabljues/projects/project/project/') in foo.py before any other imports happen, or by running export PYTHONPATH="${PYTHONPATH}:/home/dabljues/projects/project/project/" in your shell before run.sh.


Installing the project

Since it looks like you're developing a library, you might as well use the mechanisms python offers to make libraries shareable and thereby fixing any import issues. Add a minimal setup.py to the project root (i.e. /home/dabljues/projects/project/project/setup.py):

from setuptools import setup, find_packages


setup(
    name='project',
    version='0.1.0',
    packages=find_packages('project'),
)

And install your project in editable mode:

$ python3 -m pip install -e .

This will put a link in your python3 executable's site-packages that points to the project root, which makes it accessible whenever you run anything with python3.


Tests

I included print(__name__) at the top of all python files to get some output.

running run.sh without installing the package:

$ sh run.sh 
Traceback (most recent call last):
  File "project/C/C/foo.py", line 1, in <module>
    from project.A.somelib import somefunc
ModuleNotFoundError: No module named 'project'

after installing it

$ sh run.sh 
__main__
project.A.somelib

As you can see, project.C.C.foo is executed as a script, yet it finds all imports that start with project because project is installed.

Arne
  • 17,706
  • 5
  • 83
  • 99
  • Wow, thanks for a clarification. I cannot use sys.path inserting. Then I cannot use exporting PYTHONPATH, as every script in it's directory has to be a standalone script. I got to have an option to run it from root, but also from it's own directory – dabljues Aug 20 '19 at 10:31
  • So, will installing it be a valid solution? – Arne Aug 20 '19 at 10:34
  • Oh, I forgot. No sudo priviliges for me, so definitely not – dabljues Aug 20 '19 at 10:46
  • well.. from a packaging point of view I'm afraid that the right answer would be "scripts shouldn't be part of a library/package". – Arne Aug 20 '19 at 11:30
3

Run python in package mode helps.

1) Add __init__.py for every path:

.
├── project
│   ├── A
│   │   ├── __init__.py
│   │   └── somelib.py
│   ├── B
│   ├── C
│   │   ├── __init__.py
│   │   └── C
│   │       ├── foo.py
│   │       └── __init__.py
│   └── __init__.py
└── run.sh

2) Import module with relative path in foo.py:

from ...A.somelib import somefunc

3) Run python in package mode:

python -m project.C.C.foo

It works for me.

openxxs
  • 146
  • 3
  • Okay, it works. But I had to update every other import in `foo.py`. For example: `from bar import baz` -> `from .bar import baz`. And I think it doesn't work when I would try to run it from it's own directory – dabljues Aug 20 '19 at 10:32
  • Yes, you cannot run it from it's own directory. sys.path.append() is the only solution I can figure out for your situation. – openxxs Aug 20 '19 at 10:41
0

I'm unable to reproduce this given your code (assuming run.sh just starts a script).

Are you sure it's not a case of e.g. circular imports?

$ mkdir -p project/A project/C/C
$ cat > project/C/C/foo.py
print('moof!')
$ cat > project/A/somelib.py
print('beef!')
$ cat > script.py
import project.A.somelib
import project.C.C.foo
$ tree
.
├── project
│   ├── A
│   │   └── somelib.py
│   └── C
│       └── C
│           └── foo.py
└── script.py
$ python3 script.py
beef!
moof!
AKX
  • 152,115
  • 15
  • 115
  • 172
  • 1
    Well, yeah, but you're importing those from root dir. And as you can see, I am importing `project.A.somelib` in file `project/C/C/foo.py`. Doesn't that change anything? I tried your method and it works, but I don't import anything at the root level, I do it in nested files – dabljues Aug 20 '19 at 09:46
0

Change your run.sh script to run foo as a module. python3 -m proj.C.C.foo

user1302884
  • 783
  • 1
  • 8
  • 16
  • If I do this, I get: `/usr/bin/python3: No module named project/C/C/foo`. And if I add extension, so its `python3 -m proj.C.C.foo.py`, I get: `/usr/bin/python3: Error while finding module specification for 'project/C/C/foo.py' (ModuleNotFoundError: No module named 'project/C/C/foo.py)` – dabljues Aug 20 '19 at 10:02
  • 1) Module option should be used with a . and not / 2) No extension should be used So, it should be as python3 -m proj.C.C.foo and not python3 -m proj.C.C.foo.py. No .py extension. Just copy paste what was written in original answer. – user1302884 Aug 20 '19 at 10:41