pythondjangopex

How can I resolve NotADirectoryError when calling runserver on pex of a Django site?


I am building a pex bundle of a Django site as follows:

$ pipenv-pex --entry-point "manage.main"

The resulting project.pex runs as expected for runserver --help:

$ ./project.pex runserver --help
usage: toptal_joglog.pex runserver [-h] [--ipv6] [--nothreading] [--noreload]
                                   [--nostatic] [--insecure] [--version]
                                   [-v {0,1,2,3}] [--settings SETTINGS]
...

But if I try to actually run the server, I get a NotADirectoryError (traceback below). What directory is execute_from_command_line looking for (it seems to be finding the __main__ method in the pex file?) and how can I correctly invoke it?

Watching for file changes with StatReloader
Performing system checks...

Traceback (most recent call last):
  File "/home/user/Documents/project/project.pex/.bootstrap/pex/pex.py", line 396, in execute
  File "/home/user/Documents/project/project.pex/.bootstrap/pex/pex.py", line 328, in _wrap_coverage
  File "/home/user/Documents/project/project.pex/.bootstrap/pex/pex.py", line 359, in _wrap_profiling
  File "/home/user/Documents/project/project.pex/.bootstrap/pex/pex.py", line 447, in _execute
  File "/home/user/Documents/project/project.pex/.bootstrap/pex/pex.py", line 544, in execute_entry
  File "/home/user/Documents/project/project.pex/.bootstrap/pex/pex.py", line 559, in execute_pkg_resources
  File "/home/user/Documents/project/project.pex/manage.py", line 18, in main
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/core/management/__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/core/management/__init__.py", line 395, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/core/management/base.py", line 328, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/core/management/commands/runserver.py", line 60, in execute
    super().execute(*args, **options)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/core/management/base.py", line 369, in execute
    output = self.handle(*args, **options)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/core/management/commands/runserver.py", line 95, in handle
    self.run(**options)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/core/management/commands/runserver.py", line 102, in run
    autoreload.run_with_reloader(self.inner_run, **options)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 599, in run_with_reloader
    start_django(reloader, main_func, *args, **kwargs)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 584, in start_django
    reloader.run(django_main_thread)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 299, in run
    self.run_loop()
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 305, in run_loop
    next(ticker)
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 345, in tick
    for filepath, mtime in self.snapshot_files():
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 361, in snapshot_files
    for file in self.watched_files():
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 260, in watched_files
    yield from iter_all_python_module_files()
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 105, in iter_all_python_module_files
    return iter_modules_and_files(modules, frozenset(_error_files))
  File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/autoreload.py", line 141, in iter_modules_and_files
    resolved_path = path.resolve(strict=True).absolute()
  File "/usr/lib/python3.7/pathlib.py", line 1161, in resolve
    s = self._flavour.resolve(self, strict=strict)
  File "/usr/lib/python3.7/pathlib.py", line 361, in resolve
    return _resolve(base, str(path)) or sep
  File "/usr/lib/python3.7/pathlib.py", line 345, in _resolve
    target = accessor.readlink(newpath)
  File "/usr/lib/python3.7/pathlib.py", line 443, in readlink
    return os.readlink(path)
NotADirectoryError: [Errno 20] Not a directory: '/home/user/Documents/project/project.pex/__main__.py'

Solution

  • runserver is a demo/development webserver that automatically reloads Python code. One of its features is reloading Python code, so that you can make changes without needing to restarting your local development instance. This feature wasn't really designed with the context of pex file layout in mind.

    You can work around this problem by disabling the reload functionality:

    ./project.pex runserver --noreload
    

    ..at which point you will likely run into issues loading files relative to BASE_DIR:

    System check identified no issues (0 silenced).
    Traceback (most recent call last):
      File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/db/backends/base/base.py", line 220, in ensure_connection
        self.connect()
      File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/asyncio.py", line 26, in inner
        return func(*args, **kwargs)
      File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/db/backends/base/base.py", line 197, in connect
        self.connection = self.get_new_connection(conn_params)
      File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/utils/asyncio.py", line 26, in inner
        return func(*args, **kwargs)
      File "/home/user/.pex/installed_wheels/76e2a7db05161ac57adcee837113974bb4f07b41/Django-3.0.6-py3-none-any.whl/django/db/backends/sqlite3/base.py", line 199, in get_new_connection
        conn = Database.connect(**conn_params)
    sqlite3.OperationalError: unable to open database file
    

    You can work around these problems by updating how BASE_DIR is calculated in settings.py:

    # when running from source
    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    
    # when running from pex
    while os.path.isfile(BASE_DIR):
       BASE_DIR = os.path.dirname(BASE_DIR)
    

    In general, the process believes it is running in a regular folder, which is not the case. Further workarounds may be required.