Skip to content

Commit 3adfee2

Browse files
committed
Adding check that all public modules are documented.
Fixes #714.
1 parent 0c74a59 commit 3adfee2

File tree

3 files changed

+141
-2
lines changed

3 files changed

+141
-2
lines changed

scripts/run_pylint.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
violations (hence it has a reduced number of style checks).
2222
"""
2323

24+
from __future__ import print_function
25+
2426
import ConfigParser
2527
import copy
2628
import os
@@ -211,7 +213,7 @@ def lint_fileset(filenames, rcfile, description):
211213
if status_code != 0:
212214
error_message = ('Pylint failed on %s with '
213215
'status %d.' % (description, status_code))
214-
print >> sys.stderr, error_message
216+
print(error_message, file=sys.stderr)
215217
sys.exit(status_code)
216218
else:
217219
print 'Skipping %s, no files to lint.' % (description,)
@@ -229,7 +231,7 @@ def main():
229231
raise
230232

231233
message = 'Restricted lint failed, expanding to full fileset.'
232-
print >> sys.stderr, message
234+
print(message, file=sys.stderr)
233235
all_files, _ = get_files_for_linting(allow_limited=False)
234236
library_files, non_library_files, _ = get_python_files(
235237
all_files=all_files)

scripts/verify_included_modules.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Copyright 2016 Google Inc. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Check if all public modules are included in our docs."""
16+
17+
18+
from __future__ import print_function
19+
20+
import os
21+
import sys
22+
import warnings
23+
24+
from sphinx.ext.intersphinx import fetch_inventory
25+
26+
27+
BASE_DIR = os.path.abspath(
28+
os.path.join(os.path.dirname(__file__), '..'))
29+
DOCS_DIR = os.path.join(BASE_DIR, 'docs')
30+
OBJECT_INVENTORY_RELPATH = os.path.join('_build', 'html', 'objects.inv')
31+
IGNORED_PREFIXES = ('test_', '_')
32+
IGNORED_MODULES = frozenset([
33+
'gcloud.bigquery.query',
34+
'gcloud.bigtable.client',
35+
'gcloud.bigtable.cluster',
36+
'gcloud.bigtable.column_family',
37+
'gcloud.bigtable.happybase.connection',
38+
'gcloud.bigtable.row',
39+
'gcloud.bigtable.table',
40+
'gcloud.datastore.demo.demo',
41+
'gcloud.demo',
42+
'gcloud.storage.demo.demo',
43+
])
44+
45+
46+
class SphinxApp(object):
47+
"""Mock app to interact with Sphinx helpers."""
48+
warn = warnings.warn
49+
srcdir = DOCS_DIR
50+
51+
52+
def is_valid_module(filename):
53+
"""Determines if a filename is a valid Python module.
54+
55+
Assumes if is just the end of a path (i.e. does not contain
56+
``os.path.sep``.
57+
58+
:type filename: string
59+
:param filename: The name of a file.
60+
61+
:rtype: bool
62+
:returns: Flag indicating if the filename is valid.
63+
"""
64+
if not filename.endswith('.py'):
65+
return False
66+
for prefix in IGNORED_PREFIXES:
67+
if filename.startswith(prefix):
68+
return False
69+
return True
70+
71+
72+
def get_public_modules(path, base_package=None):
73+
"""Get list of all public modules relative to a path.
74+
75+
:type path: string
76+
:param path: The path containing the python modules.
77+
78+
:type base_package: string
79+
:param base_package: (Optional) A package to prepend in
80+
front of the path.
81+
82+
:rtype: list
83+
:returns: List of all modules found.
84+
"""
85+
result = []
86+
for subdir, _, files in os.walk(path):
87+
# Skip folders that start with _.
88+
if any([part.startswith('_')
89+
for part in subdir.split(os.path.sep)]):
90+
continue
91+
_, rel_dir = subdir.split(path)
92+
rel_dir = rel_dir.lstrip(os.path.sep)
93+
for filename in files:
94+
if is_valid_module(filename):
95+
mod_name, _ = os.path.splitext(filename)
96+
rel_path = os.path.join(rel_dir, mod_name)
97+
if base_package is not None:
98+
rel_path = os.path.join(base_package, rel_path)
99+
# Turn into a Python module rather than a file path.
100+
result.append(rel_path.replace(os.path.sep, '.'))
101+
102+
return result
103+
104+
105+
def main():
106+
"""Main script to verify modules included."""
107+
mock_uri = ''
108+
inventory = fetch_inventory(SphinxApp, mock_uri,
109+
OBJECT_INVENTORY_RELPATH)
110+
sphinx_mods = set(inventory['py:module'].keys())
111+
112+
library_dir = os.path.join(BASE_DIR, 'gcloud')
113+
public_mods = get_public_modules(library_dir,
114+
base_package='gcloud')
115+
public_mods = set(public_mods)
116+
117+
if not sphinx_mods <= public_mods:
118+
message = ('Unexpected error. There were modules referenced by '
119+
'Sphinx that are not among the public modules.')
120+
print(message, file=sys.stderr)
121+
sys.exit(1)
122+
123+
undocumented_mods = public_mods - sphinx_mods
124+
# Remove ignored modules.
125+
undocumented_mods -= IGNORED_MODULES
126+
if undocumented_mods:
127+
message_parts = ['Found undocumented public modules:']
128+
message_parts.extend(['- ' + mod_name
129+
for mod_name in sorted(undocumented_mods)])
130+
print('\n'.join(message_parts), file=sys.stderr)
131+
sys.exit(1)
132+
133+
134+
if __name__ == '__main__':
135+
main()

tox.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ basepython =
4545
commands =
4646
python -c "import shutil; shutil.rmtree('docs/_build', ignore_errors=True)"
4747
sphinx-build -W -b html -d docs/_build/doctrees docs docs/_build/html
48+
python {toxinidir}/scripts/verify_included_modules.py
4849
deps =
4950
Sphinx
5051
passenv = {[testenv:system-tests]passenv} SPHINX_RELEASE READTHEDOCS LOCAL_RTD
@@ -57,6 +58,7 @@ basepython = {[testenv:docs]basepython}
5758
commands =
5859
python -c "import shutil; shutil.rmtree('docs/_build_rtd', ignore_errors=True)"
5960
sphinx-build -W -b html -d docs/_build_rtd/doctrees docs docs/_build_rtd/html
61+
python {toxinidir}/scripts/verify_included_modules.py
6062
deps = {[testenv:docs]deps}
6163
passenv = {[testenv:docs]passenv}
6264

0 commit comments

Comments
 (0)