3

Building the sphinx documentation of a project importing PyQt5 fails (build log) with

QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-docs'
qt.qpa.screen: QXcbConnection: Could not connect to display 
Could not connect to any X display.

In tox.ini the following was needed:

[testenv:docs]
# avoid QStandardPaths: XDG_RUNTIME_DIR not set
passenv = XDG_RUNTIME_DIR
# xvfb-run prevents Could not connect to any X display
commands = /usr/bin/xvfb-run sphinx-build --color -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html

How to do that on readthedocs ?

This is closely related to PyQt 4 import in read-the-docs, which unfortunately did not contain the error messages. And PyQt5 is installable from pip.

Notes:

  • in the advanced settings, Install your project inside a virtualenv using setup.py install is checked (but unchecking did not help).
  • the reference geoptics snapshot for the following tentatives is f33d233bf67bd7922ec864635e7589e7f4feb40f

Tentatives

1. With mock module

Maybe mocking PyQT5 could work. But this seems a bit cumbersome.

Adapting from this answer, adding

import mock 
MOCK_MODULES = ['sip', 'PyQt5', 'PyQt5.QtGui', 'PyQt5.QtCore', 'PyQt5.QtWidgets']
sys.modules.update((mod_name, mock.MagicMock()) for mod_name in MOCK_MODULES)

to conf.py yields

    class _GRay(GCounterPart, QGraphicsPathItem):
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

2. With the sphinx built-in autodoc_mock_imports

same error with the simpler (just one line added to conf.py)

autodoc_mock_imports = ['sip', 'PyQt5', 'PyQt5.QtGui', 'PyQt5.QtCore', 'PyQt5.QtWidgets']

3. With custom Mock

Using julen's custom Mock class

class Mock(object):
    def __init__(self, *args, **kwargs):
        pass

    def __call__(self, *args, **kwargs):
        return Mock()

    @classmethod
    def __getattr__(cls, name):
        if name in ('__file__', '__path__'):
            return '/dev/null'
        elif name[0] == name[0].upper():
            mockType = type(name, (), {})
            mockType.__module__ = __name__
            return mockType
        else:
            return Mock()

MOCK_MODULES = ['sip', 'PyQt5', 'PyQt5.QtGui', 'PyQt5.QtCore', 'PyQt5.QtWidgets']
for mod_name in MOCK_MODULES:
    sys.modules[mod_name] = Mock()

yields

  File ".../geoptics/guis/qt/main.py", line 59, in <module>
    app = QCoreApplication.instance()
AttributeError: type object 'QCoreApplication' has no attribute 'instance'

It should be possible to move the app definition/retrieval stuff from the module level to a function body, not executed at module import.

4. autodoc_mock_imports without multiple inheritance

autodoc_mock_imports = ['sip', 'PyQt5', 'PyQt5.QtGui', 'PyQt5.QtCore', 'PyQt5.QtWidgets']

in conf.py, as in the 2nd tentative, but multiple inheritance replaced with decorators. The changes are described in this pull request.

Now the error is

geoptics.guis.qt.handles.LineHandle.reset_move:1:term not in glossary: move restrictions

because the geoptics class _GScene(QGraphicsScene) where the term is defined has been mocked away by sphinx, and its documentation lost.


Comments left in relevant issues of:

ederag
  • 2,409
  • 25
  • 49
  • 1
    Avoiding multiple inheritance will definitely remove the problem as far as mocking PyQt goes. Not sure if that's the only solution though – three_pineapples May 07 '18 at 11:05
  • After some struggle, because `isclass` is false for mocked classes, decorators instead of multiple inheritance do allow to use `autodoc_mock_imports`. Unfortunately sphinx mocks all the classes inheriting from PyQt5, and their documentation is lost. Which brings back to the original question: is it possible to make readthedocs pass `XDG_RUNTIME_DIR` and use `xvfb-run` ? – ederag May 07 '18 at 22:45
  • 1
    I can't find any evidence that `xvfb` is even going to be available in the build environment even if you do manage to pass that environment variable. There is pretty much no way to get around mocking PyQt. What exactly do you want to be generate from PyQt documentation? As far as I'm aware, there isn't actually that much available for sphinx to use. Perhaps getting `intersphinx` working would be enough? If so, see [this](https://stackoverflow.com/a/49079027/1994235). I managed to make the base class listings in your docs link to the Qt C++ docs using the custom .inv file in that post. – three_pineapples May 08 '18 at 07:40
  • 1
    Alternatively, does using `conda` help? see [here](http://read-the-docs.readthedocs.io/en/latest/conda.html) – three_pineapples May 08 '18 at 07:43
  • @three_pineapples Thanks for the link (already had upvoted the question, but not yet seen the answer). This will be useful later on. The current issue is with GeOptics classes and their documentation. Adding the tentative 4 will hopefully make that clearer. A new [issue](https://github.com/ederag/GeOptics/issues/1) has been opened and might be more comfortable for discussion, if you wish. – ederag May 08 '18 at 12:14
  • @three_pineapples Thanks for the `conda` link. After reading the docs, it seems to concern packaging, which is not the issue here (readthedocs perfectly installs PyQt5 from pip). I left comments in two relevant issues (added at the bottom of the question); we'll see. – ederag May 10 '18 at 20:34

1 Answers1

1

The autodoc_mock_imports has been fixed in sphinx-1.7.5.

In docs/conf.py add the following line:

autodoc_mock_imports = ['sip', 'PyQt5', 'PyQt5.QtGui', 'PyQt5.QtCore', 'PyQt5.QtWidgets']

Then, create a docs/requirements.txt with a single line

sphinx>=1.7.5

and declare docs/requirements.txt in the readthedocs project admin>advanced settings>Requirements file.

Happily, this does not bypass the setup.py, it just adds the sphinx-1.7.5 version requirement.

ederag
  • 2,409
  • 25
  • 49