Added support for implicit namespace packages

implicit_namespaces
Ashley Whetter 6 years ago committed by Ashley Whetter
parent 03f601ed41
commit cd7f66cdc3

@ -215,6 +215,10 @@ class SphinxMapperBase(object):
ignore = [] ignore = []
files_to_read = [] files_to_read = []
for _dir in dirs: for _dir in dirs:
if os.path.isfile(_dir):
files_to_read.append(_dir)
continue
for root, dirnames, filenames in os.walk(_dir): for root, dirnames, filenames in os.walk(_dir):
for pattern in patterns: for pattern in patterns:
for filename in fnmatch.filter(filenames, pattern): for filename in fnmatch.filter(filenames, pattern):

@ -1,6 +1,6 @@
import sys
import os
import collections import collections
import functools
import os
import astroid import astroid
import sphinx import sphinx
@ -11,8 +11,35 @@ from . import astroid_utils
from ..utils import slugify from ..utils import slugify
class PythonSphinxMapper(SphinxMapperBase): def resolve_module_name(root_dir, path):
"""Get the module name for a given path, relative to a root directory.
:param root_dir: The directory that the module or package is relative to.
:type root_dir: str
:param path: The path the find the module name for.
:type path: str
:returns: The name of the module, namespace, or package.
:rtype: str
"""
if os.path.isfile(root_dir):
name = os.path.basename(path)
else:
real_root = os.path.abspath(os.path.join(root_dir, os.pardir))
name = os.path.relpath(path, real_root)
name = name.replace(os.sep, '.')
exts = (os.sep + '__init__.py', '.py')
for ext in exts:
if name.endswith(ext):
name = name[:-len(ext)]
break
return name
class PythonSphinxMapper(SphinxMapperBase):
"""Auto API domain handler for Python """Auto API domain handler for Python
Parses directly from Python files. Parses directly from Python files.
@ -21,29 +48,28 @@ class PythonSphinxMapper(SphinxMapperBase):
""" """
def load(self, patterns, dirs, ignore=None): def load(self, patterns, dirs, ignore=None):
"""Load objects from the filesystem into the ``paths`` dictionary """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 dir_ in dirs:
dir_root = dir_
if os.path.exists(os.path.join(dir_, '__init__.py')):
dir_root = os.path.abspath(os.path.join(dir_, os.pardir))
for path in self.find_files(patterns=patterns, dirs=[dir_], ignore=ignore): for path in self.find_files(patterns=patterns, dirs=[dir_], ignore=ignore):
data = self.read_file(path=path) module_name = resolve_module_name(dir_, path)
data = self.read_file(path=path, module_name=module_name)
if data: if data:
data['relative_path'] = os.path.relpath(path, dir_root)
self.paths[path] = data self.paths[path] = data
def read_file(self, path, **kwargs): if not os.path.isfile(os.path.join(dir_, '__init__.py')):
self.paths[dir_] = {
'type': 'namespace',
'name': os.path.basename(dir_),
'doc': '',
}
def read_file(self, path, module_name, **kwargs):
"""Read file input into memory, returning deserialized objects """Read file input into memory, returning deserialized objects
:param path: Path of file to read :param path: Path of file to read
""" """
try: try:
parsed_data = Parser().parse_file(path) parsed_data = Parser().parse_file(path, module_name)
return parsed_data return parsed_data
except (IOError, TypeError, ImportError): except (IOError, TypeError, ImportError):
self.app.warn('Error reading file: {0}'.format(path)) self.app.warn('Error reading file: {0}'.format(path))
@ -55,10 +81,11 @@ class PythonSphinxMapper(SphinxMapperBase):
parents = {obj.name: obj for obj in self.objects.values()} parents = {obj.name: obj for obj in self.objects.values()}
for obj in self.objects.values(): for obj in self.objects.values():
parent_name = obj.name.rsplit('.', 1)[0] parent_name = obj.name.rsplit('.', 1)[0]
if parent_name in parents and parent_name != obj.name: if parent_name in parents:
parent = parents[parent_name] if parent_name != obj.name:
attr = 'sub{}s'.format(obj.type) parent = parents[parent_name]
getattr(parent, attr).append(obj) attr = 'sub{}s'.format(obj.type)
getattr(parent, attr).append(obj)
for obj in self.objects.values(): for obj in self.objects.values():
obj.submodules.sort() obj.submodules.sort()
@ -72,7 +99,7 @@ class PythonSphinxMapper(SphinxMapperBase):
obj_map = dict((cls.type, cls) for cls obj_map = dict((cls.type, cls) for cls
in [PythonClass, PythonFunction, PythonModule, in [PythonClass, PythonFunction, PythonModule,
PythonMethod, PythonPackage, PythonAttribute, PythonMethod, PythonPackage, PythonAttribute,
PythonData, PythonException]) PythonData, PythonException, PythonNamespace])
try: try:
cls = obj_map[data['type']] cls = obj_map[data['type']]
except KeyError: except KeyError:
@ -234,7 +261,6 @@ class TopLevelPythonPythonMapper(PythonPythonMapper):
def __init__(self, obj, **kwargs): def __init__(self, obj, **kwargs):
super(TopLevelPythonPythonMapper, self).__init__(obj, **kwargs) super(TopLevelPythonPythonMapper, self).__init__(obj, **kwargs)
self._resolve_name()
self.top_level_object = '.' not in self.name self.top_level_object = '.' not in self.name
self.subpackages = [] self.subpackages = []
@ -252,29 +278,13 @@ class TopLevelPythonPythonMapper(PythonPythonMapper):
class PythonModule(TopLevelPythonPythonMapper): class PythonModule(TopLevelPythonPythonMapper):
type = 'module' type = 'module'
def _resolve_name(self):
name = self.obj['relative_path']
name = name.replace(os.sep, '.')
ext = '.py'
if name.endswith(ext):
name = name[:-len(ext)]
self.name = name
class PythonPackage(TopLevelPythonPythonMapper): class PythonPackage(TopLevelPythonPythonMapper):
type = 'package' type = 'package'
def _resolve_name(self):
name = self.obj['relative_path']
exts = [os.sep + '__init__.py', '.py']
for ext in exts:
if name.endswith(ext):
name = name[:-len(ext)]
name = name.replace(os.sep, '.')
self.name = name class PythonNamespace(TopLevelPythonPythonMapper):
type = 'namespace'
class PythonClass(PythonPythonMapper): class PythonClass(PythonPythonMapper):
@ -355,16 +365,7 @@ class PythonException(PythonClass):
class Parser(object): class Parser(object):
def parse_file(self, file_path): def parse_file(self, file_path, module_name):
directory, filename = os.path.split(file_path)
module_part = os.path.splitext(filename)[0]
module_parts = collections.deque([module_part])
while os.path.isfile(os.path.join(directory, '__init__.py')):
directory, module_part = os.path.split(directory)
if module_part:
module_parts.appendleft(module_part)
module_name = '.'.join(module_parts)
node = astroid.MANAGER.ast_from_file(file_path, module_name) node = astroid.MANAGER.ast_from_file(file_path, module_name)
return self.parse(node) return self.parse(node)
@ -477,7 +478,7 @@ class Parser(object):
path = node.path[0] if node.path else None path = node.path[0] if node.path else None
type_ = 'module' type_ = 'module'
if path.endswith('__init__.py'): if node.package:
type_ = 'package' type_ = 'package'
data = { data = {

@ -0,0 +1 @@
{% extends "python/module.rst" %}

@ -10,8 +10,7 @@ Configuration Options
Paths (relative or absolute) to the source code that you wish to generate your API documentation from. Paths (relative or absolute) to the source code that you wish to generate your API documentation from.
If a package directory is specified, the package directory itself will be included in the relative path of the For documenting Python, the package directory must be specified.
children. If an ordinary directory is specified, that directory will not be included in the relative path.
.. confval:: autoapi_type .. confval:: autoapi_type

@ -17,5 +17,5 @@ html_static_path = ['_static']
htmlhelp_basename = 'pyexampledoc' htmlhelp_basename = 'pyexampledoc'
extensions = ['sphinx.ext.autodoc', 'autoapi.extension'] extensions = ['sphinx.ext.autodoc', 'autoapi.extension']
autoapi_type = 'python' autoapi_type = 'python'
autoapi_dirs = ['example'] autoapi_dirs = ['example/example.py']
autoapi_file_pattern = '*.py' autoapi_file_pattern = '*.py'

@ -4,7 +4,7 @@
contain the root `toctree` directive. contain the root `toctree` directive.
Welcome to pypackageexample's documentation! Welcome to pypackageexample's documentation!
===================================== ============================================
.. toctree:: .. toctree::

@ -17,8 +17,8 @@ html_static_path = ['_static']
htmlhelp_basename = 'pyexampledoc' htmlhelp_basename = 'pyexampledoc'
extensions = ['autoapi.extension'] extensions = ['autoapi.extension']
autoapi_type = 'python' autoapi_type = 'python'
autoapi_dirs = ['example'] autoapi_dirs = ['example/example.py']
autoapi_file_pattern = '*.py' autoapi_file_pattern = '*.py'
autoapi_template_dir = 'template_overrides' autoapi_template_dir = 'template_overrides'
exclude_patterns = [autoapi_template_dir] exclude_patterns = [autoapi_template_dir]

@ -17,5 +17,5 @@ html_static_path = ['_static']
htmlhelp_basename = 'pyexampledoc' htmlhelp_basename = 'pyexampledoc'
extensions = ['autoapi.extension'] extensions = ['autoapi.extension']
autoapi_type = 'python' autoapi_type = 'python'
autoapi_dirs = ['example'] autoapi_dirs = ['example/example.py']
autoapi_file_pattern = '*.py' autoapi_file_pattern = '*.py'

Loading…
Cancel
Save