Search code examples
pythondjangogitlabpython-sphinxgitlab-ci-runner

Cannot build sphinx autodocumentation with Django via GitLab (docker) pipeline


this problem might be quite specific.

My setup

This is my project structure. It's a Django project.

├── docs
│   ├── build
│   │   ├── doctrees
│   │   └── html
│   ├── Makefile
│   └── source
│       ├── code
│       ├── conf.py
│       ├── contributing.md
│       ├── index.rst
│       ├── infrastructure.md
│       └── _static
├── my-project-name
│   ├── api.py
│   ├── asgi.py
│   ├── celeryconfig.py
│   ├── celery.py
│   ├── __init__.py
│   ├── __pycache__
│   ├── routers.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
|   ... some more apps

It is hosted on a private GitLab instance and I have multiple runners installed. One is a docker executor that is responsible for building the documentation via Sphinx.

The basic .gitlab-ci.yml looks like this:

image: python:3.10-slim

stages:
- deploy

pages:
  tags:
  - docs
  stage: deploy
  script:
  - python3 -m pip install django sphinx furo myst-parser
  - sphinx-build -b html docs/source public/

This has been working just fine as long as I haven't tried to include Django code via the sphinx-autodoc extension. Everything shows up on the GitLab pages. I know that Sphinx needs to load every module that it scans upfront. So this is why you have to initialize Django in the conf.py.

The beginning of my Sphinx conf.py looks like this:


# Sphinx needs Django loaded to correctly use the "autodoc" extension
import django
import os
import sys
from pathlib import Path

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my-project-name.settings")
sys.path.append(Path(__file__).parent.parent.parent)

django.setup()

The last line before calling django.setup() makes sure to include the project-level path. Autodocumenting Django works fine with these modifications when I make html on my local system. The modules I specify are documented nicely and no errors occur.

Now to my actual problem

I push the code to my GitLab repository and let the jobs run via a docker executor.

$ python3 -m pip install django sphinx furo myst-parser
Collecting django
  Downloading Django-4.1.7-py3-none-any.whl (8.1 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 8.1/8.1 MB 124.3 MB/s eta 0:00:00
Collecting sphinx
  Downloading sphinx-6.1.3-py3-none-any.whl (3.0 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.0/3.0 MB 111.0 MB/s eta 0:00:00
...
# the loading continues here

After all the modules are loaded it tries to execute Sphinx. Now, this error occurs:


WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
[notice] A new release of pip available: 22.3.1 -> 23.0.1
[notice] To update, run: pip install --upgrade pip
$ sphinx-build -b html docs/source public/
Running Sphinx v6.1.3
######
/builds/my-dir/my-project-name
######
Configuration error:
There is a programmable error in your configuration file:
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/sphinx/config.py", line 351, in eval_config_file
    exec(code, namespace)  # NoQA: S102
  File "/builds/my-dir/my-project-name/docs/source/conf.py", line 22, in <module>
    django.setup()
  File "/usr/local/lib/python3.10/site-packages/django/__init__.py", line 19, in setup
    configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)
  File "/usr/local/lib/python3.10/site-packages/django/conf/__init__.py", line 92, in __getattr__
    self._setup(name)
  File "/usr/local/lib/python3.10/site-packages/django/conf/__init__.py", line 79, in _setup
    self._wrapped = Settings(settings_module)
  File "/usr/local/lib/python3.10/site-packages/django/conf/__init__.py", line 190, in __init__
    mod = importlib.import_module(self.SETTINGS_MODULE)
  File "/usr/local/lib/python3.10/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 992, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1050, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1027, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1004, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'my-project-name'
Cleaning up project directory and file based variables
00:01
ERROR: Job failed: exit code 1

As far as I know, this usually occurs when you don't set your path variables correctly. But I set it at the project level and print it out as validation (the ### lines in the error message). My project path is definitely /builds/my-dir/my-project-name.

Do you have any ideas on what I could try to make it work?


Solution

  • Ok - I'm a major idiot.

    sys.path.append(Path(__file__).parent.parent.parent.as_posix())
    

    saved the day.