2016-11-02 23:29:28 +00:00
|
|
|
import sys
|
2016-08-25 23:24:14 +00:00
|
|
|
import os
|
2016-11-02 23:29:28 +00:00
|
|
|
import re
|
2016-08-25 23:24:14 +00:00
|
|
|
import textwrap
|
2016-11-02 23:29:28 +00:00
|
|
|
import ast
|
2016-10-25 23:26:30 +00:00
|
|
|
from collections import defaultdict
|
|
|
|
from pydocstyle.parser import Parser
|
2015-04-08 05:54:53 +00:00
|
|
|
|
2016-11-02 23:29:28 +00:00
|
|
|
if sys.version_info < (3,):
|
|
|
|
from itertools import izip_longest as zip_longest
|
|
|
|
else:
|
|
|
|
from itertools import zip_longest
|
|
|
|
|
2015-06-10 18:53:09 +00:00
|
|
|
from .base import PythonMapperBase, SphinxMapperBase
|
2016-06-09 22:43:55 +00:00
|
|
|
from ..utils import slugify
|
2015-04-14 22:59:09 +00:00
|
|
|
|
2015-04-21 05:54:32 +00:00
|
|
|
|
2015-06-10 21:23:50 +00:00
|
|
|
class PythonSphinxMapper(SphinxMapperBase):
|
2015-04-21 05:54:32 +00:00
|
|
|
|
2016-11-02 23:29:28 +00:00
|
|
|
"""Auto API domain handler for Python
|
2015-04-21 05:54:32 +00:00
|
|
|
|
|
|
|
Parses directly from Python files.
|
|
|
|
|
|
|
|
:param app: Sphinx application passed in as part of the extension
|
2016-11-02 23:29:28 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
def load(self, patterns, dirs, **kwargs):
|
|
|
|
"""Load objects from the filesystem into the ``paths`` dictionary
|
|
|
|
|
|
|
|
Also include an attribute on the object, ``relative_path`` which is the
|
|
|
|
shortened, relative path the package/module
|
|
|
|
"""
|
|
|
|
for dir_ in dirs:
|
|
|
|
for path in self.find_files(patterns=patterns, dirs=[dir_], **kwargs):
|
|
|
|
data = self.read_file(path=path)
|
|
|
|
data.relative_path = os.path.relpath(path, dir_)
|
|
|
|
if data:
|
|
|
|
self.paths[path] = data
|
2015-04-21 05:54:32 +00:00
|
|
|
|
2015-06-06 23:11:49 +00:00
|
|
|
def read_file(self, path, **kwargs):
|
2016-11-02 23:29:28 +00:00
|
|
|
"""Read file input into memory, returning deserialized objects
|
2015-06-06 23:11:49 +00:00
|
|
|
|
|
|
|
:param path: Path of file to read
|
2016-11-02 23:29:28 +00:00
|
|
|
"""
|
2015-06-06 23:11:49 +00:00
|
|
|
try:
|
2016-10-20 21:57:07 +00:00
|
|
|
parsed_data = Parser()(open(path), path)
|
2015-06-06 23:11:49 +00:00
|
|
|
return parsed_data
|
2016-11-02 23:29:28 +00:00
|
|
|
except (IOError, TypeError, ImportError):
|
2015-07-07 23:19:25 +00:00
|
|
|
self.app.warn('Error reading file: {0}'.format(path))
|
2015-06-06 23:11:49 +00:00
|
|
|
return None
|
|
|
|
|
2016-11-02 23:29:28 +00:00
|
|
|
def create_class(self, data, options=None, path=None, **kwargs):
|
2016-10-25 23:26:30 +00:00
|
|
|
"""Create a class from the passed in data
|
2015-04-21 05:54:32 +00:00
|
|
|
|
2016-06-09 22:43:55 +00:00
|
|
|
:param data: dictionary data of pydocstyle output
|
2016-10-25 23:26:30 +00:00
|
|
|
"""
|
2015-05-31 05:03:19 +00:00
|
|
|
obj_map = dict((cls.type, cls) for cls
|
2016-11-02 23:29:28 +00:00
|
|
|
in [PythonClass, PythonFunction, PythonModule,
|
|
|
|
PythonMethod, PythonPackage])
|
2015-05-31 05:03:19 +00:00
|
|
|
try:
|
2016-06-09 22:43:55 +00:00
|
|
|
cls = obj_map[data.kind]
|
2015-05-31 05:03:19 +00:00
|
|
|
except KeyError:
|
2016-11-02 23:29:28 +00:00
|
|
|
self.app.warn("Unknown type: %s" % data.kind)
|
2015-05-31 05:03:19 +00:00
|
|
|
else:
|
2016-08-25 23:24:14 +00:00
|
|
|
obj = cls(data, jinja_env=self.jinja_env,
|
2016-11-02 23:29:28 +00:00
|
|
|
options=self.app.config.autoapi_options, **kwargs)
|
2016-06-09 22:43:55 +00:00
|
|
|
for child_data in data.children:
|
2016-11-02 23:29:28 +00:00
|
|
|
for child_obj in self.create_class(child_data, options=options,
|
|
|
|
**kwargs):
|
2016-06-09 22:43:55 +00:00
|
|
|
obj.children.append(child_obj)
|
2015-05-31 05:03:19 +00:00
|
|
|
yield obj
|
2015-04-21 05:54:32 +00:00
|
|
|
|
2015-04-08 05:54:53 +00:00
|
|
|
|
2015-06-10 21:23:50 +00:00
|
|
|
class PythonPythonMapper(PythonMapperBase):
|
2015-04-08 05:54:53 +00:00
|
|
|
|
|
|
|
language = 'python'
|
2016-11-02 23:29:28 +00:00
|
|
|
is_callable = False
|
2015-04-08 05:54:53 +00:00
|
|
|
|
2015-06-10 18:01:06 +00:00
|
|
|
def __init__(self, obj, **kwargs):
|
2015-06-10 21:23:50 +00:00
|
|
|
super(PythonPythonMapper, self).__init__(obj, **kwargs)
|
2015-06-06 23:11:49 +00:00
|
|
|
|
2016-11-02 23:29:28 +00:00
|
|
|
self.name = self._get_full_name(obj)
|
|
|
|
self.id = slugify(self.name)
|
2015-04-08 05:54:53 +00:00
|
|
|
|
2015-04-21 05:54:32 +00:00
|
|
|
# Optional
|
|
|
|
self.children = []
|
2016-11-02 23:29:28 +00:00
|
|
|
self.args = []
|
|
|
|
if self.is_callable:
|
|
|
|
self.args = self._get_arguments(obj)
|
2016-06-09 22:43:55 +00:00
|
|
|
self.docstring = obj.docstring or ''
|
2016-08-25 23:24:14 +00:00
|
|
|
self.docstring = textwrap.dedent(self.docstring)
|
2016-06-09 22:43:55 +00:00
|
|
|
self.docstring = self.docstring.replace("'''", '').replace('"""', '')
|
|
|
|
if getattr(obj, 'parent'):
|
|
|
|
self.inheritance = [obj.parent.name]
|
|
|
|
else:
|
|
|
|
self.inheritance = ''
|
2015-04-08 05:54:53 +00:00
|
|
|
|
2015-04-21 05:54:32 +00:00
|
|
|
# For later
|
|
|
|
self.item_map = defaultdict(list)
|
2015-04-08 05:54:53 +00:00
|
|
|
|
2015-06-10 20:33:42 +00:00
|
|
|
@property
|
2016-11-02 23:29:28 +00:00
|
|
|
def is_undoc_member(self):
|
2015-06-10 20:33:42 +00:00
|
|
|
return self.docstring == ''
|
|
|
|
|
|
|
|
@property
|
2016-11-02 23:29:28 +00:00
|
|
|
def is_private_member(self):
|
2015-06-10 20:58:52 +00:00
|
|
|
return self.short_name[0] == '_'
|
2015-06-10 20:33:42 +00:00
|
|
|
|
|
|
|
@property
|
2016-11-02 23:29:28 +00:00
|
|
|
def is_special_member(self):
|
2015-06-10 20:58:52 +00:00
|
|
|
return self.short_name[0:2] == '__'
|
2015-06-10 20:33:42 +00:00
|
|
|
|
2015-06-10 20:58:52 +00:00
|
|
|
@property
|
|
|
|
def display(self):
|
2016-11-02 23:29:28 +00:00
|
|
|
if self.is_undoc_member and 'undoc-members' not in self.options:
|
2015-06-10 20:33:42 +00:00
|
|
|
return False
|
2016-11-02 23:29:28 +00:00
|
|
|
if self.is_private_member and 'private-members' not in self.options:
|
2015-06-10 20:33:42 +00:00
|
|
|
return False
|
2016-11-02 23:29:28 +00:00
|
|
|
if self.is_special_member and 'special-members' not in self.options:
|
2015-06-10 20:33:42 +00:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2016-11-02 23:29:28 +00:00
|
|
|
@staticmethod
|
|
|
|
def _get_full_name(obj):
|
|
|
|
"""Recursively build the full name of the object from pydocstyle
|
|
|
|
|
|
|
|
Uses an additional attribute added to the object, ``relative_path``.
|
|
|
|
This is the shortened path of the object name, if the object is a
|
|
|
|
package or module.
|
|
|
|
|
|
|
|
:param obj: pydocstyle object, as returned from Parser()
|
|
|
|
:returns: Dotted name of object
|
|
|
|
:rtype: str
|
|
|
|
"""
|
|
|
|
|
|
|
|
def _inner(obj, parts=[]):
|
|
|
|
obj_kind = obj.kind
|
|
|
|
obj_name = obj.name
|
|
|
|
if obj_kind == 'module':
|
|
|
|
obj_name = getattr(obj, 'relative_path', None) or obj.name
|
|
|
|
obj_name = obj_name.replace('/', '.')
|
|
|
|
ext = '.py'
|
|
|
|
if obj_name.endswith(ext):
|
|
|
|
obj_name = obj_name[:-len(ext)]
|
|
|
|
elif obj_kind == 'package':
|
|
|
|
obj_name = getattr(obj, 'relative_path', None) or obj.name
|
|
|
|
exts = ['/__init__.py', '.py']
|
|
|
|
for ext in exts:
|
|
|
|
if obj_name.endswith(ext):
|
|
|
|
obj_name = obj_name[:-len(ext)]
|
|
|
|
obj_name = obj_name.split('/').pop()
|
|
|
|
parts.insert(0, obj_name)
|
|
|
|
try:
|
|
|
|
return _inner(obj.parent, parts)
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
return parts
|
|
|
|
|
|
|
|
return '.'.join(_inner(obj))
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def _get_arguments(obj):
|
|
|
|
"""Get arguments from a pydocstyle object
|
|
|
|
|
|
|
|
:param obj: pydocstyle object, as returned from Parser()
|
|
|
|
:returns: list of argument or argument and value pairs
|
|
|
|
:rtype: list
|
|
|
|
"""
|
|
|
|
arguments = []
|
|
|
|
source = textwrap.dedent(obj.source)
|
|
|
|
# Bare except here because AST parsing can throw any number of
|
|
|
|
# exceptions, including SyntaxError
|
|
|
|
try:
|
|
|
|
parsed = ast.parse(source)
|
|
|
|
except: # noqa
|
|
|
|
return
|
|
|
|
parsed_args = parsed.body[0].args
|
|
|
|
arg_names = [arg.id if sys.version_info < (3,) else arg.arg
|
|
|
|
for arg in parsed_args.args]
|
|
|
|
|
|
|
|
# Get defaults for display based on AST node type
|
|
|
|
arg_defaults = []
|
|
|
|
pydocstyle_map = {
|
|
|
|
ast.Name: 'id',
|
|
|
|
ast.Num: 'n',
|
|
|
|
ast.Str: lambda obj: '"{0}"'.format(obj.s),
|
|
|
|
ast.Call: lambda obj: obj.func.id,
|
|
|
|
# TODO these require traversal into the AST nodes. Add this for more
|
|
|
|
# complete argument parsing, or handle with a custom AST traversal.
|
|
|
|
ast.List: lambda _: 'list',
|
|
|
|
ast.Tuple: lambda _: 'tuple',
|
|
|
|
ast.Set: lambda _: 'set',
|
|
|
|
ast.Dict: lambda _: 'dict',
|
|
|
|
}
|
|
|
|
if sys.version_info >= (3,):
|
|
|
|
pydocstyle_map.update({
|
|
|
|
ast.NameConstant: 'value',
|
|
|
|
})
|
|
|
|
|
|
|
|
for value in parsed_args.defaults:
|
|
|
|
default = None
|
|
|
|
try:
|
|
|
|
default = pydocstyle_map[type(value)](value)
|
|
|
|
except TypeError:
|
|
|
|
default = getattr(value, pydocstyle_map[type(value)])
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
if default is None:
|
|
|
|
default = 'None'
|
|
|
|
arg_defaults.append(default)
|
|
|
|
|
|
|
|
# Apply defaults padded to the end of the longest list. AST returns
|
|
|
|
# argument defaults as a short array that applies to the end of the list
|
|
|
|
# of arguments
|
|
|
|
for (name, default) in zip_longest(reversed(arg_names),
|
|
|
|
reversed(arg_defaults)):
|
|
|
|
arg = name
|
|
|
|
if default is not None:
|
|
|
|
arg = '{0}={1}'.format(name, default)
|
|
|
|
arguments.insert(0, arg)
|
|
|
|
|
|
|
|
# Add *args and **kwargs
|
|
|
|
if parsed_args.vararg:
|
|
|
|
arguments.append('*{0}'.format(
|
|
|
|
parsed_args.vararg
|
|
|
|
if sys.version_info < (3,3)
|
|
|
|
else parsed_args.vararg.arg
|
|
|
|
))
|
|
|
|
if parsed_args.kwarg:
|
|
|
|
arguments.append('**{0}'.format(
|
|
|
|
parsed_args.kwarg
|
|
|
|
if sys.version_info < (3,3)
|
|
|
|
else parsed_args.kwarg.arg
|
|
|
|
))
|
|
|
|
|
|
|
|
return arguments
|
|
|
|
|
2015-04-08 05:54:53 +00:00
|
|
|
|
2015-06-10 21:23:50 +00:00
|
|
|
class PythonFunction(PythonPythonMapper):
|
2015-04-21 05:54:32 +00:00
|
|
|
type = 'function'
|
2016-11-02 23:29:28 +00:00
|
|
|
is_callable = True
|
2015-04-08 05:54:53 +00:00
|
|
|
|
2015-04-21 05:54:32 +00:00
|
|
|
|
2016-06-09 22:43:55 +00:00
|
|
|
class PythonMethod(PythonPythonMapper):
|
|
|
|
type = 'method'
|
2016-11-02 23:29:28 +00:00
|
|
|
is_callable = True
|
2016-06-09 22:43:55 +00:00
|
|
|
|
|
|
|
|
2015-06-10 21:23:50 +00:00
|
|
|
class PythonModule(PythonPythonMapper):
|
2015-04-21 05:54:32 +00:00
|
|
|
type = 'module'
|
2015-06-10 20:13:34 +00:00
|
|
|
top_level_object = True
|
2015-04-21 05:54:32 +00:00
|
|
|
|
|
|
|
|
2016-08-25 23:24:14 +00:00
|
|
|
class PythonPackage(PythonPythonMapper):
|
|
|
|
type = 'package'
|
|
|
|
top_level_object = True
|
|
|
|
|
|
|
|
|
2015-06-10 21:23:50 +00:00
|
|
|
class PythonClass(PythonPythonMapper):
|
2015-04-21 05:54:32 +00:00
|
|
|
type = 'class'
|