@ -1,88 +1,92 @@
import sys
import os
import re
import textwrap
import ast
from collections import defaultdict
from pydocstyle . parser import Parser
if sys . version_info < ( 3 , ) :
from itertools import izip_longest as zip_longest
else :
from itertools import zip_longest
from . base import PythonMapperBase , SphinxMapperBase
from . . utils import slugify
class PythonSphinxMapper ( SphinxMapperBase ) :
''' Auto API domain handler for Python
""" Auto API domain handler for Python
Parses directly from Python files .
: param app : Sphinx application passed in as part of the extension
'''
"""
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
def read_file ( self , path , * * kwargs ) :
''' Read file input into memory, returning deserialized objects
""" Read file input into memory, returning deserialized objects
: param path : Path of file to read
'''
"""
try :
parsed_data = Parser ( ) ( open ( path ) , path )
return parsed_data
except IOError :
self . app . warn ( ' Error reading file: {0} ' . format ( path ) )
except TypeError :
self . app . warn ( ' Error reading file: {0} ' . format ( path ) )
except ImportError :
except ( IOError , TypeError , ImportError ) :
self . app . warn ( ' Error reading file: {0} ' . format ( path ) )
return None
def create_class ( self , data , options = None , * * kwargs ) :
def create_class ( self , data , options = None , path = None , * * kwargs ) :
""" Create a class from the passed in data
: param data : dictionary data of pydocstyle output
"""
obj_map = dict ( ( cls . type , cls ) for cls
in [ PythonClass , PythonFunction , PythonModule , PythonMethod , PythonPackage ] )
in [ PythonClass , PythonFunction , PythonModule ,
PythonMethod , PythonPackage ] )
try :
cls = obj_map [ data . kind ]
except KeyError :
self . app . warn ( " Unknown T ype: %s " % data . kind )
self . app . warn ( " Unknown t ype: %s " % data . kind )
else :
path = kwargs . get ( ' path ' )
obj = cls ( data , jinja_env = self . jinja_env ,
options = self . app . config . autoapi_options , path = path
)
options = self . app . config . autoapi_options , * * kwargs )
for child_data in data . children :
for child_obj in self . create_class ( child_data , options = options , path = path ) :
for child_obj in self . create_class ( child_data , options = options ,
* * kwargs ) :
obj . children . append ( child_obj )
self . add_object ( child_obj )
yield obj
class PythonPythonMapper ( PythonMapperBase ) :
language = ' python '
is_callable = False
def __init__ ( self , obj , * * kwargs ) :
super ( PythonPythonMapper , self ) . __init__ ( obj , * * kwargs )
# Properly name the object with dot notation
if self . top_level_object :
name = self . path . relative . split ( ' . ' ) [ 0 ] . replace ( ' / ' , ' . ' )
else :
name = ' . ' . join ( [
os . path . dirname ( self . path . relative ) . replace ( ' / ' , ' . ' ) ,
obj . name
] )
self . id = slugify ( name )
self . name = name
self . name = self . _get_full_name ( obj )
self . id = slugify ( self . name )
# Optional
self . children = [ ]
try :
args = obj . source . split ( ' \n ' ) [ 0 ]
args = args . split ( ' ( ' ) [ 1 ]
args = args . split ( ' ) ' ) [ 0 ]
self . args = args . split ( ' , ' )
except :
args = ' '
self . args = [ ]
if self . is_callable :
self . args = self . _get_arguments ( obj )
self . docstring = obj . docstring or ' '
self . docstring = textwrap . dedent ( self . docstring )
self . docstring = self . docstring . replace ( " ' ' ' " , ' ' ) . replace ( ' " " " ' , ' ' )
@ -95,34 +99,151 @@ class PythonPythonMapper(PythonMapperBase):
self . item_map = defaultdict ( list )
@property
def undoc_member( self ) :
def is_ undoc_member( self ) :
return self . docstring == ' '
@property
def private_member( self ) :
def is_ private_member( self ) :
return self . short_name [ 0 ] == ' _ '
@property
def special_member( self ) :
def is_ special_member( self ) :
return self . short_name [ 0 : 2 ] == ' __ '
@property
def display ( self ) :
if self . undoc_member and ' undoc-members ' not in self . options :
if self . is_ undoc_member and ' undoc-members ' not in self . options :
return False
if self . private_member and ' private-members ' not in self . options :
if self . is_ private_member and ' private-members ' not in self . options :
return False
if self . special_member and ' special-members ' not in self . options :
if self . is_ special_member and ' special-members ' not in self . options :
return False
return True
@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
class PythonFunction ( PythonPythonMapper ) :
type = ' function '
is_callable = True
class PythonMethod ( PythonPythonMapper ) :
type = ' method '
is_callable = True
class PythonModule ( PythonPythonMapper ) :