Skip to content

Commit 62abb6a

Browse files
authored
Fix mgmt documentation generation (Azure#30046)
* dynamically discover multi-apis instead of relying on static list * add sphinx keyword to known words list
1 parent 4318a6a commit 62abb6a

File tree

3 files changed

+92
-54
lines changed

3 files changed

+92
-54
lines changed

.vscode/cspell.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,12 @@
401401
"aiter"
402402
],
403403
"overrides": [
404+
{
405+
"filename": "doc/sphinx/generate_doc.py",
406+
"words": [
407+
"undoc"
408+
]
409+
},
404410
{
405411
"filename": "doc/dev/test_proxy_migration_guide.md",
406412
"words": [

doc/sphinx/generate_doc.py

Lines changed: 82 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,21 @@
33
import json
44
from pathlib import Path
55
import os
6+
import glob
67

7-
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), './package_service_mapping.json')
8-
GENERATED_PACKAGES_LIST_FILE = 'toc_tree.rst'
8+
import typing
9+
from typing import Dict, List
10+
11+
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "package_service_mapping.json")
12+
GENERATED_PACKAGES_LIST_FILE = "toc_tree.rst"
913

1014
_LOGGER = logging.getLogger(__name__)
1115

16+
1217
def make_title(title):
1318
"""Create a underlined title with the correct number of =."""
14-
return "\n".join([title, len(title)*"="])
19+
return "\n".join([title, len(title) * "="])
20+
1521

1622
SUBMODULE_TEMPLATE = """{title}
1723
@@ -85,14 +91,42 @@ def make_title(title):
8591
"azure.mgmt.resource.subscriptions",
8692
]
8793

88-
def generate_doc(config_path, output_directory = "./ref/", project_pattern=None):
89-
multiapi_found_apiversion = {}
90-
91-
rst_path_template = os.path.join(output_directory, '{}.rst')
92-
rst_namespace_template = os.path.join(output_directory, '{}.{}.rst')
9394

95+
def get_updated_config(config_path: str, package_root: str) -> Dict[str, Dict[str, typing.Union[str, List[str]]]]:
9496
with Path(config_path).open() as config_fd:
9597
config = json.load(config_fd)
98+
99+
namespace_folders = os.path.basename(package_root).split("-")
100+
package_name = "-".join(namespace_folders)
101+
namespace = ".".join(namespace_folders)
102+
103+
api_directory = os.path.join(package_root, *namespace_folders)
104+
glob_path = os.path.join(api_directory, "v20*/")
105+
valid_versions = glob.glob(glob_path)
106+
107+
for version_path in valid_versions:
108+
api_version = os.path.relpath(version_path, start=api_directory)
109+
full_namespace = namespace + f".{api_version}"
110+
111+
if package_name in config:
112+
if "namespaces" in config[package_name]:
113+
ns = config[package_name]["namespaces"]
114+
if ns:
115+
if full_namespace not in ns:
116+
ns.append(full_namespace)
117+
118+
return config
119+
120+
121+
def generate_doc(config_path: str, output_directory: str = "./ref/", package_root: str = None) -> None:
122+
multiapi_found_apiversion = {}
123+
# we are handed a directory that looks like <path-to-repo>/sdk/containerservice/azure-mgmt-containerservice/
124+
project_pattern = os.path.basename(package_root).replace("-", ".")
125+
rst_path_template = os.path.join(output_directory, "{}.rst")
126+
rst_namespace_template = os.path.join(output_directory, "{}.{}.rst")
127+
128+
config = get_updated_config(config_path, package_root)
129+
96130
package_list_path = []
97131

98132
namespaces = [n for pack in config.values() for n in pack.get("namespaces", {})]
@@ -105,25 +139,22 @@ def generate_doc(config_path, output_directory = "./ref/", project_pattern=None)
105139
_LOGGER.info("Working on %s", namespace)
106140

107141
rst_path = rst_path_template.format(namespace)
108-
with Path(rst_path).open('w') as rst_file:
109-
rst_file.write(PACKAGE_TEMPLATE.format(
110-
title=make_title(namespace+" package"),
111-
namespace=namespace
112-
))
142+
with Path(rst_path).open("w") as rst_file:
143+
rst_file.write(PACKAGE_TEMPLATE.format(title=make_title(namespace + " package"), namespace=namespace))
113144

114145
for module in ["operations", "models"]:
115-
with Path(rst_namespace_template.format(namespace, module)).open('w') as rst_file:
116-
rst_file.write(SUBMODULE_TEMPLATE.format(
117-
title=make_title(namespace+"."+module+" module"),
118-
namespace=namespace,
119-
submodule=module
120-
))
146+
with Path(rst_namespace_template.format(namespace, module)).open("w") as rst_file:
147+
rst_file.write(
148+
SUBMODULE_TEMPLATE.format(
149+
title=make_title(namespace + "." + module + " module"), namespace=namespace, submodule=module
150+
)
151+
)
121152

122153
for multiapi_namespace in MULTIAPI_VERSION_NAMESPACE:
123154
length = len(multiapi_namespace.split("."))
124155
if namespace.split(".")[0:length] == multiapi_namespace.split(".")[0:length]:
125156
_LOGGER.info("MultiAPI namespace on %s", multiapi_namespace)
126-
api_package = namespace.split(multiapi_namespace+".")[1]
157+
api_package = namespace.split(multiapi_namespace + ".")[1]
127158
multiapi_found_apiversion.setdefault(multiapi_namespace, []).append(api_package)
128159
break
129160
else:
@@ -133,47 +164,47 @@ def generate_doc(config_path, output_directory = "./ref/", project_pattern=None)
133164
apilist.sort()
134165
apilist.reverse()
135166
rst_path = rst_path_template.format(multiapi_namespace)
136-
with Path(rst_path).open('w') as rst_file:
137-
rst_file.write(MULTIAPI_VERSION_PACKAGE_TEMPLATE.format(
138-
title=make_title(multiapi_namespace+" package"),
139-
namespace=multiapi_namespace
140-
))
167+
with Path(rst_path).open("w") as rst_file:
168+
rst_file.write(
169+
MULTIAPI_VERSION_PACKAGE_TEMPLATE.format(
170+
title=make_title(multiapi_namespace + " package"), namespace=multiapi_namespace
171+
)
172+
)
141173
for version in apilist:
142-
rst_file.write(" {namespace}.{version}\n".format(
143-
namespace=multiapi_namespace,
144-
version=version))
174+
rst_file.write(" {namespace}.{version}\n".format(namespace=multiapi_namespace, version=version))
145175
package_list_path.append(rst_path)
146176

147-
148177
# now handle the packages from data plane that we want to be present properly sorted in the list of packages
149178
for package in config.keys():
150-
if 'manually_generated' in config[package] and config[package]['manually_generated'] == True:
151-
package_list_path.append(rst_path_template.format(package.replace('-','.')))
179+
if "manually_generated" in config[package] and config[package]["manually_generated"] == True:
180+
package_list_path.append(rst_path_template.format(package.replace("-", ".")))
152181

153182
package_list_path.sort()
154-
with Path(GENERATED_PACKAGES_LIST_FILE).open('w') as generate_file_list_fd:
155-
lines_to_write = "\n".join([" "+package for package in package_list_path])
183+
with Path(GENERATED_PACKAGES_LIST_FILE).open("w") as generate_file_list_fd:
184+
lines_to_write = "\n".join([" " + package for package in package_list_path])
156185
generate_file_list_fd.write(RST_AUTODOC_TOCTREE.format(generated_packages=lines_to_write))
157186

187+
158188
def main():
159-
parser = argparse.ArgumentParser(
160-
description='Generate documentation file.'
189+
parser = argparse.ArgumentParser(description="Generate sphinx api stubs for one or multiple management packages.")
190+
parser.add_argument(
191+
"--project",
192+
"-p",
193+
dest="project",
194+
help="Want to target a specific management package? Pass the targeted package root to this argument.",
195+
)
196+
parser.add_argument(
197+
"--config",
198+
"-c",
199+
dest="config_path",
200+
default=CONFIG_FILE,
201+
help="The JSON configuration format path [default: %(default)s]",
202+
)
203+
parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbosity in INFO mode")
204+
parser.add_argument("--debug", dest="debug", action="store_true", help="Verbosity in DEBUG mode")
205+
parser.add_argument(
206+
"--out", "-o", dest="output_directory", help="The final resting place for the generated sphinx source files."
161207
)
162-
parser.add_argument('--project', '-p',
163-
dest='project', action='append',
164-
help='Select a specific project. Do all by default. You can use a substring for several projects.')
165-
parser.add_argument('--config', '-c',
166-
dest='config_path', default=CONFIG_FILE,
167-
help='The JSON configuration format path [default: %(default)s]')
168-
parser.add_argument("-v", "--verbose",
169-
dest="verbose", action="store_true",
170-
help="Verbosity in INFO mode")
171-
parser.add_argument("--debug",
172-
dest="debug", action="store_true",
173-
help="Verbosity in DEBUG mode")
174-
parser.add_argument("--out", "-o",
175-
dest="output_directory",
176-
help="The final resting place for the generated sphinx source files.")
177208

178209
args = parser.parse_args()
179210

@@ -184,5 +215,6 @@ def main():
184215

185216
generate_doc(args.config_path, args.output_directory, args.project)
186217

218+
187219
if __name__ == "__main__":
188220
main()

eng/tox/run_sphinx_apidoc.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ def sphinx_apidoc(working_directory):
6666
)
6767
exit(1)
6868

69-
def mgmt_apidoc(working_directory, namespace):
69+
def mgmt_apidoc(working_directory: str, target_folder: str):
7070
command_array = [
7171
sys.executable,
7272
generate_mgmt_script,
7373
"-p",
74-
namespace,
74+
target_folder,
7575
"-o",
7676
working_directory,
77-
"--verbose"
77+
"--verbose",
7878
]
7979

8080
try:
@@ -122,7 +122,7 @@ def mgmt_apidoc(working_directory, namespace):
122122

123123
if should_build_docs(pkg_details.name):
124124
if is_mgmt_package(pkg_details.name):
125-
mgmt_apidoc(output_directory, pkg_details.namespace)
125+
mgmt_apidoc(output_directory, pkg_details.folder)
126126
else:
127127
sphinx_apidoc(args.working_directory)
128128
else:

0 commit comments

Comments
 (0)