mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Old get_module_source API restored, new version moved to ModuleAnalyzer class, tests updated
This commit is contained in:
parent
c4e60b5b9c
commit
0a982d5ebd
@ -11,8 +11,9 @@
|
|||||||
import re
|
import re
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from os import path
|
from os import path
|
||||||
from typing import Any, Dict, IO, List, Tuple
|
from typing import Any, Dict, IO, List, Tuple, Optional
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
from importlib import import_module
|
||||||
|
|
||||||
from sphinx.errors import PycodeError
|
from sphinx.errors import PycodeError
|
||||||
from sphinx.pycode.parser import Parser
|
from sphinx.pycode.parser import Parser
|
||||||
@ -23,6 +24,55 @@ class ModuleAnalyzer:
|
|||||||
# cache for analyzer objects -- caches both by module and file name
|
# cache for analyzer objects -- caches both by module and file name
|
||||||
cache = {} # type: Dict[Tuple[str, str], Any]
|
cache = {} # type: Dict[Tuple[str, str], Any]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_module_source(modname: str) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
"""Try to find the source code for a module.
|
||||||
|
|
||||||
|
Returns ('filename', 'source'). One of it can be None if
|
||||||
|
no filename or source found
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mod = import_module(modname)
|
||||||
|
except Exception as err:
|
||||||
|
raise PycodeError('error importing %r' % modname, err)
|
||||||
|
loader = getattr(mod, '__loader__', None)
|
||||||
|
filename = getattr(mod, '__file__', None)
|
||||||
|
if loader and getattr(loader, 'get_source', None):
|
||||||
|
# prefer Native loader, as it respects #coding directive
|
||||||
|
try:
|
||||||
|
source = loader.get_source(modname)
|
||||||
|
if source:
|
||||||
|
# no exception and not None - it must be module source
|
||||||
|
return filename, source
|
||||||
|
except ImportError as err:
|
||||||
|
pass # Try other "source-mining" methods
|
||||||
|
if filename is None and loader and getattr(loader, 'get_filename', None):
|
||||||
|
# have loader, but no filename
|
||||||
|
try:
|
||||||
|
filename = loader.get_filename(modname)
|
||||||
|
except ImportError as err:
|
||||||
|
raise PycodeError('error getting filename for %r' % modname, err)
|
||||||
|
if filename is None:
|
||||||
|
# all methods for getting filename failed, so raise...
|
||||||
|
raise PycodeError('no source found for module %r' % modname)
|
||||||
|
filename = path.normpath(path.abspath(filename))
|
||||||
|
lfilename = filename.lower()
|
||||||
|
if lfilename.endswith('.pyo') or lfilename.endswith('.pyc'):
|
||||||
|
filename = filename[:-1]
|
||||||
|
if not path.isfile(filename) and path.isfile(filename + 'w'):
|
||||||
|
filename += 'w'
|
||||||
|
elif not (lfilename.endswith('.py') or lfilename.endswith('.pyw')):
|
||||||
|
raise PycodeError('source is not a .py file: %r' % filename)
|
||||||
|
elif ('.egg' + os.path.sep) in filename:
|
||||||
|
pat = '(?<=\\.egg)' + re.escape(os.path.sep)
|
||||||
|
eggpath, _ = re.split(pat, filename, 1)
|
||||||
|
if path.isfile(eggpath):
|
||||||
|
return filename, None
|
||||||
|
|
||||||
|
if not path.isfile(filename):
|
||||||
|
raise PycodeError('source file is not present: %r' % filename)
|
||||||
|
return filename, None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def for_string(cls, string: str, modname: str, srcname: str = '<string>'
|
def for_string(cls, string: str, modname: str, srcname: str = '<string>'
|
||||||
) -> "ModuleAnalyzer":
|
) -> "ModuleAnalyzer":
|
||||||
@ -63,7 +113,7 @@ class ModuleAnalyzer:
|
|||||||
return entry
|
return entry
|
||||||
|
|
||||||
try:
|
try:
|
||||||
filename, source = get_module_source(modname)
|
filename, source = cls.get_module_source(modname)
|
||||||
if source is not None:
|
if source is not None:
|
||||||
obj = cls.for_string(source, modname, filename if filename is not None else '<string>')
|
obj = cls.for_string(source, modname, filename if filename is not None else '<string>')
|
||||||
elif filename is not None:
|
elif filename is not None:
|
||||||
|
@ -25,7 +25,7 @@ from hashlib import md5
|
|||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from os import path
|
from os import path
|
||||||
from time import mktime, strptime
|
from time import mktime, strptime
|
||||||
from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, Set, Tuple, Optional
|
from typing import Any, Callable, Dict, IO, Iterable, Iterator, List, Pattern, Set, Tuple
|
||||||
from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode
|
from urllib.parse import urlsplit, urlunsplit, quote_plus, parse_qsl, urlencode
|
||||||
|
|
||||||
from docutils.utils import relative_path
|
from docutils.utils import relative_path
|
||||||
@ -265,35 +265,31 @@ def save_traceback(app: "Sphinx") -> str:
|
|||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def get_module_source(modname: str) -> Tuple[Optional[str], Optional[str]]:
|
def get_module_source(modname: str) -> Tuple[str, str]:
|
||||||
"""Try to find the source code for a module.
|
"""Try to find the source code for a module.
|
||||||
|
|
||||||
Returns ('filename', 'source'). One of it can be None if
|
Can return ('file', 'filename') in which case the source is in the given
|
||||||
no filename or source found
|
file, or ('string', 'source') which which case the source is the string.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
mod = import_module(modname)
|
mod = import_module(modname)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise PycodeError('error importing %r' % modname, err)
|
raise PycodeError('error importing %r' % modname, err)
|
||||||
loader = getattr(mod, '__loader__', None)
|
|
||||||
filename = getattr(mod, '__file__', None)
|
filename = getattr(mod, '__file__', None)
|
||||||
if loader and getattr(loader, 'get_source', None):
|
loader = getattr(mod, '__loader__', None)
|
||||||
# prefer Native loader, as it respects #coding directive
|
if loader and getattr(loader, 'get_filename', None):
|
||||||
try:
|
|
||||||
source = loader.get_source(modname)
|
|
||||||
if source:
|
|
||||||
# no exception and not None - it must be module source
|
|
||||||
return filename, source
|
|
||||||
except ImportError as err:
|
|
||||||
pass # Try other "source-mining" methods
|
|
||||||
if filename is None and loader and getattr(loader, 'get_filename', None):
|
|
||||||
# have loader, but no filename
|
|
||||||
try:
|
try:
|
||||||
filename = loader.get_filename(modname)
|
filename = loader.get_filename(modname)
|
||||||
except ImportError as err:
|
except Exception as err:
|
||||||
raise PycodeError('error getting filename for %r' % modname, err)
|
raise PycodeError('error getting filename for %r' % filename, err)
|
||||||
|
if filename is None and loader:
|
||||||
|
try:
|
||||||
|
filename = loader.get_source(modname)
|
||||||
|
if filename:
|
||||||
|
return 'string', filename
|
||||||
|
except Exception as err:
|
||||||
|
raise PycodeError('error getting source for %r' % modname, err)
|
||||||
if filename is None:
|
if filename is None:
|
||||||
# all methods for getting filename failed, so raise...
|
|
||||||
raise PycodeError('no source found for module %r' % modname)
|
raise PycodeError('no source found for module %r' % modname)
|
||||||
filename = path.normpath(path.abspath(filename))
|
filename = path.normpath(path.abspath(filename))
|
||||||
lfilename = filename.lower()
|
lfilename = filename.lower()
|
||||||
@ -307,11 +303,11 @@ def get_module_source(modname: str) -> Tuple[Optional[str], Optional[str]]:
|
|||||||
pat = '(?<=\\.egg)' + re.escape(os.path.sep)
|
pat = '(?<=\\.egg)' + re.escape(os.path.sep)
|
||||||
eggpath, _ = re.split(pat, filename, 1)
|
eggpath, _ = re.split(pat, filename, 1)
|
||||||
if path.isfile(eggpath):
|
if path.isfile(eggpath):
|
||||||
return filename, None
|
return 'file', filename
|
||||||
|
|
||||||
if not path.isfile(filename):
|
if not path.isfile(filename):
|
||||||
raise PycodeError('source file is not present: %r' % filename)
|
raise PycodeError('source file is not present: %r' % filename)
|
||||||
return filename, None
|
return 'file', filename
|
||||||
|
|
||||||
|
|
||||||
def get_full_modname(modname: str, attribute: str) -> str:
|
def get_full_modname(modname: str, attribute: str) -> str:
|
||||||
|
@ -10,12 +10,23 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import pytest
|
||||||
|
|
||||||
import sphinx
|
import sphinx
|
||||||
from sphinx.pycode import ModuleAnalyzer
|
from sphinx.pycode import ModuleAnalyzer
|
||||||
|
from sphinx.errors import PycodeError
|
||||||
|
|
||||||
SPHINX_MODULE_PATH = os.path.splitext(sphinx.__file__)[0] + '.py'
|
SPHINX_MODULE_PATH = os.path.splitext(sphinx.__file__)[0] + '.py'
|
||||||
|
|
||||||
|
def test_ModuleAnalyzer_get_module_source():
|
||||||
|
assert ModuleAnalyzer.get_module_source('sphinx') == (sphinx.__file__, sphinx.__loader__.get_source('sphinx'))
|
||||||
|
|
||||||
|
# failed to obtain source information from builtin modules
|
||||||
|
with pytest.raises(PycodeError):
|
||||||
|
ModuleAnalyzer.get_module_source('builtins')
|
||||||
|
with pytest.raises(PycodeError):
|
||||||
|
ModuleAnalyzer.get_module_source('itertools')
|
||||||
|
|
||||||
|
|
||||||
def test_ModuleAnalyzer_for_string():
|
def test_ModuleAnalyzer_for_string():
|
||||||
analyzer = ModuleAnalyzer.for_string('print("Hello world")', 'module_name')
|
analyzer = ModuleAnalyzer.for_string('print("Hello world")', 'module_name')
|
||||||
|
@ -62,7 +62,7 @@ def test_display_chunk():
|
|||||||
|
|
||||||
|
|
||||||
def test_get_module_source():
|
def test_get_module_source():
|
||||||
assert get_module_source('sphinx') == (sphinx.__file__, sphinx.__loader__.get_source('sphinx'))
|
assert get_module_source('sphinx') == ('file', sphinx.__file__)
|
||||||
|
|
||||||
# failed to obtain source information from builtin modules
|
# failed to obtain source information from builtin modules
|
||||||
with pytest.raises(PycodeError):
|
with pytest.raises(PycodeError):
|
||||||
|
Loading…
Reference in New Issue
Block a user