mon
mon

Reputation: 22234

Python/Flask - Cause of module not found in the docker container

The src/online_serving/main.py cannot find lib/common/util_string.py although they are in the same /app directory in which Python process is executed.

/app# python3 src/online_serving/main.py
Traceback (most recent call last):
  File "/app/src/online_serving/main.py", line 18, in <module>
    from lib.common import util_string
ModuleNotFoundError: No module named 'lib'

My directory layout is fairly straightforward. The Docker image is created using the Dockerfile at the BASE directory.

BASE
├── Dockerfile
├── lib
│   ├── __init__.py
│   ├── common
│   │   ├── __init__.py
│   │   └── util_string.py
├── src
│   ├── online_serving
│   │   ├── __init__.py
│   │   ├── main.py

My Dockerfile contains:

FROM python:3.9-slim
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./
ENTRYPOINT gunicorn --bind :$PORT --timeout 120 src.online_serving.main:app

main.py imports the lib.common.util_string which is causing error.

from flask import (
    Flask,
    request,
    Response
)

from lib.common import util_string


# Flask runtime
app = Flask(__name__)

As a workaround, I can set this line in the Dockerfile

ENV PYTHONPATH /app

But why isn't the PYTHONPATH automatically set to the /app directory in the container?

Upvotes: 0

Views: 637

Answers (1)

mon
mon

Reputation: 22234

When the Python interpreter is started with python a/b/c.py, the current directory is not added in sys.path but the directory where the script c.py is.

$ python a/b/c.py
$ pwd
/app
 
$ tree
a
└── b
    └── c.py
 
$ cat a/b/c.py
import sys
for p in sys.path:
    print(p)
 
$ python3 a/b/c.py
/app/a/b                                # <--- path[0] is the directory of the script used to invoke the interpreter.
/usr/local/lib/python39.zip
/usr/local/lib/python3.9
/usr/local/lib/python3.9/lib-dynload
/usr/local/lib/python3.9/site-packages

sys.path

A list of strings that specifies the search path for modules. Initialized from the environment variable PYTHONPATH, plus an installation-dependent default.

As initialized upon program startup, the first item of this list,path[0], is the directory containing the script that was used to invoke the Python interpreter.

If the script directory is not available, thenpath[0] is the empty string, which directs Python to search modules in the current directory first.(e.g. if the interpreter is invoked interactively or if the script is read from standard input), Notice that the script directory is insertedbeforethe entries inserted as a result ofPYTHONPATH.

To add the current directory to sys.path so that a module is searched in the current directory tree, need to use PYTHONPATH or start the interpreter with -m option.

-m

If this option is given, the first element of sys.argv will be the full path to the module file (while the module file is being located, the first element will be set to "-m"). As with the -c option, the current directory will be added to the start of sys.path.

$ pwd
/app

$ tree
a
└── b
    └── c.py

$ export PYTHONPATH=${PYTHONPATH}:/app

$ python a/b/c.py
/app/a/b                                # <--- path[0] is the directory of the script used to invoke the interpreter.
/app                                    # <--- then from the environment variable PYTHONPATH, plus an installation-dependent default.
/usr/local/lib/python39.zip
/usr/local/lib/python3.9
/usr/local/lib/python3.9/lib-dynload
/usr/local/lib/python3.9/site-packages

Or

$ pwd
/app

$ tree
a
└── b
    └── c.py

$ cat a/b/c.py
import sys
for p in sys.path:
    print(p)

$ python3 -m a.b.c
/app                                    # <--- Current directory is path[0] for python -m <module> invocation
/usr/local/lib/python39.zip
/usr/local/lib/python3.9
/usr/local/lib/python3.9/lib-dynload
/usr/local/lib/python3.9/site-packages

Upvotes: 1

Related Questions