summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2018-09-06 21:30:56 +0900
committerTakeshi KOMIYA <i.tkomiya@gmail.com>2018-09-06 22:22:01 +0900
commit35e1764025447b8e863a68c65a666836b5099a9d (patch)
treeb4c06dd45760235a0049cb7bf90c1847175576d8
parent4abc55239a5ecd52c249bf0ede0fea44504b1fc8 (diff)
downloadsphinx-git-35e1764025447b8e863a68c65a666836b5099a9d.tar.gz
Fix #5290: autodoc: failed to analyze source code in egg package
-rw-r--r--CHANGES1
-rw-r--r--sphinx/pycode/__init__.py19
-rw-r--r--sphinx/util/__init__.py5
-rw-r--r--tests/roots/test-pycode-egg/conf.py8
-rw-r--r--tests/roots/test-pycode-egg/index.rst2
-rw-r--r--tests/roots/test-pycode-egg/sample-0.0.0-py3.7.eggbin0 -> 1365 bytes
-rw-r--r--tests/roots/test-pycode-egg/src/sample.py8
-rw-r--r--tests/roots/test-pycode-egg/src/setup.py6
-rw-r--r--tests/test_autodoc.py23
-rw-r--r--tests/test_pycode.py26
10 files changed, 97 insertions, 1 deletions
diff --git a/CHANGES b/CHANGES
index 27d3d4ab9..301e5b2f6 100644
--- a/CHANGES
+++ b/CHANGES
@@ -38,6 +38,7 @@ Bugs fixed
* autodoc: ImportError is replaced by AttributeError for deeper module
* #2720, #4034: Incorrect links with ``:download:``, duplicate names, and
parallel builds
+* #5290: autodoc: failed to analyze source code in egg package
Testing
--------
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index c4c055bf5..a97903a27 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -10,6 +10,9 @@
"""
from __future__ import print_function
+import re
+from zipfile import ZipFile
+
from six import iteritems, BytesIO, StringIO
from sphinx.errors import PycodeError
@@ -42,10 +45,24 @@ class ModuleAnalyzer(object):
obj = cls(f, modname, filename) # type: ignore
cls.cache['file', filename] = obj
except Exception as err:
- raise PycodeError('error opening %r' % filename, err)
+ if '.egg/' in filename:
+ obj = cls.cache['file', filename] = cls.for_egg(filename, modname)
+ else:
+ raise PycodeError('error opening %r' % filename, err)
return obj
@classmethod
+ def for_egg(cls, filename, modname):
+ # type: (unicode, unicode) -> ModuleAnalyzer
+ eggpath, relpath = re.split('(?<=\\.egg)/', filename)
+ try:
+ with ZipFile(eggpath) as egg:
+ code = egg.read(relpath).decode('utf-8')
+ return cls.for_string(code, modname, filename)
+ except Exception as exc:
+ raise PycodeError('error opening %r' % filename, exc)
+
+ @classmethod
def for_module(cls, modname):
# type: (str) -> ModuleAnalyzer
if ('module', modname) in cls.cache:
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py
index 29038978a..0c0c22c9c 100644
--- a/sphinx/util/__init__.py
+++ b/sphinx/util/__init__.py
@@ -314,6 +314,11 @@ def get_module_source(modname):
filename += 'w'
elif not (lfilename.endswith('.py') or lfilename.endswith('.pyw')):
raise PycodeError('source is not a .py file: %r' % filename)
+ elif '.egg' in filename:
+ eggpath, _ = re.split('(?<=\\.egg)/', filename)
+ if path.isfile(eggpath):
+ return 'file', filename
+
if not path.isfile(filename):
raise PycodeError('source file is not present: %r' % filename)
return 'file', filename
diff --git a/tests/roots/test-pycode-egg/conf.py b/tests/roots/test-pycode-egg/conf.py
new file mode 100644
index 000000000..a8e25882b
--- /dev/null
+++ b/tests/roots/test-pycode-egg/conf.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('sample-0.0.0-py3.7.egg'))
+master_doc = 'index'
+extensions = ['sphinx.ext.autodoc']
diff --git a/tests/roots/test-pycode-egg/index.rst b/tests/roots/test-pycode-egg/index.rst
new file mode 100644
index 000000000..affc7912a
--- /dev/null
+++ b/tests/roots/test-pycode-egg/index.rst
@@ -0,0 +1,2 @@
+test-pycode-egg
+===============
diff --git a/tests/roots/test-pycode-egg/sample-0.0.0-py3.7.egg b/tests/roots/test-pycode-egg/sample-0.0.0-py3.7.egg
new file mode 100644
index 000000000..719dbea51
--- /dev/null
+++ b/tests/roots/test-pycode-egg/sample-0.0.0-py3.7.egg
Binary files differ
diff --git a/tests/roots/test-pycode-egg/src/sample.py b/tests/roots/test-pycode-egg/src/sample.py
new file mode 100644
index 000000000..c4d3d61e8
--- /dev/null
+++ b/tests/roots/test-pycode-egg/src/sample.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+#: constant on sample.py
+CONSTANT = 1
+
+
+def hello(s):
+ print('Hello %s' % s)
diff --git a/tests/roots/test-pycode-egg/src/setup.py b/tests/roots/test-pycode-egg/src/setup.py
new file mode 100644
index 000000000..f23c80a06
--- /dev/null
+++ b/tests/roots/test-pycode-egg/src/setup.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from setuptools import setup
+
+
+setup(name='sample',
+ py_modules=['sample'])
diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py
index cefceb833..e2dc37c56 100644
--- a/tests/test_autodoc.py
+++ b/tests/test_autodoc.py
@@ -1584,3 +1584,26 @@ def test_autodoc_default_options_with_values(app):
assert ' list of weak references to the object (if defined)' not in actual
assert ' .. py:method:: CustomIter.snafucate()' not in actual
assert ' Makes this snafucated.' not in actual
+
+
+@pytest.mark.sphinx('html', testroot='pycode-egg')
+def test_autodoc_for_egged_code(app):
+ options = {"members": None,
+ "undoc-members": None}
+ actual = do_autodoc(app, 'module', 'sample', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: sample',
+ '',
+ '',
+ '.. py:data:: CONSTANT',
+ ' :module: sample',
+ ' :annotation: = 1',
+ '',
+ ' constant on sample.py',
+ ' ',
+ '',
+ '.. py:function:: hello(s)',
+ ' :module: sample',
+ ''
+ ]
diff --git a/tests/test_pycode.py b/tests/test_pycode.py
index b4385e8a6..2eab456bc 100644
--- a/tests/test_pycode.py
+++ b/tests/test_pycode.py
@@ -10,6 +10,7 @@
"""
import os
+import sys
from six import PY2
@@ -47,6 +48,31 @@ def test_ModuleAnalyzer_for_module():
assert analyzer.encoding == 'utf-8'
+def test_ModuleAnalyzer_for_file_in_egg(rootdir):
+ try:
+ path = rootdir / 'test-pycode-egg' / 'sample-0.0.0-py3.7.egg'
+ sys.path.insert(0, path)
+
+ import sample
+ analyzer = ModuleAnalyzer.for_file(sample.__file__, 'sample')
+ docs = analyzer.find_attr_docs()
+ assert docs == {('', 'CONSTANT'): ['constant on sample.py', '']}
+ finally:
+ sys.path.pop(0)
+
+
+def test_ModuleAnalyzer_for_module_in_egg(rootdir):
+ try:
+ path = rootdir / 'test-pycode-egg' / 'sample-0.0.0-py3.7.egg'
+ sys.path.insert(0, path)
+
+ analyzer = ModuleAnalyzer.for_module('sample')
+ docs = analyzer.find_attr_docs()
+ assert docs == {('', 'CONSTANT'): ['constant on sample.py', '']}
+ finally:
+ sys.path.pop(0)
+
+
def test_ModuleAnalyzer_find_tags():
code = ('class Foo(object):\n' # line: 1
' """class Foo!"""\n'