Skip to content

Commit a0c4de7

Browse files
update mgmt docs build to not rely on hardcoded mappings (#34482)
* hardcoding everything wip, should able to compute automatically * update the code to not use hardcoded package_service_mapping * make sure we preserve ordering of latest --> oldest * update copyright year
1 parent 6aafa16 commit a0c4de7

File tree

3 files changed

+85
-1183
lines changed

3 files changed

+85
-1183
lines changed

doc/sphinx/conf.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@
6060

6161
intersphinx_mapping = {
6262
# Dependencies
63-
'python': ('https://docs.python.org/3.11', ('/usr/share/doc/python3-doc/html/objects.inv', None)),
64-
'requests': ('https://requests.kennethreitz.org/en/master/', None),
65-
'aiohttp': ('https://aiohttp.readthedocs.io/en/stable/', None),
63+
'python': ('https://docs.python.org/3.11', None),
64+
'requests': ('https://requests.kennethreitz.org/en/latest/', None),
65+
'aiohttp': ('https://docs.aiohttp.org/en/stable/', None),
6666
'trio': ('https://trio.readthedocs.io/en/stable/', None),
6767
'msal': ('https://msal-python.readthedocs.io/en/latest/', None),
6868
# Azure packages
@@ -87,7 +87,7 @@
8787

8888
# General information about the project.
8989
project = u'Azure SDK for Python'
90-
copyright = u'2019, Microsoft'
90+
copyright = u'2024, Microsoft'
9191

9292
# The version info for the project you're documenting, acts as replacement for
9393
# |version| and |release|, also used in various other places throughout the

doc/sphinx/generate_doc.py

Lines changed: 81 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
import os
66
import glob
77

8-
import typing
98
from typing import Dict, List
109

11-
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "package_service_mapping.json")
1210
GENERATED_PACKAGES_LIST_FILE = "toc_tree.rst"
1311

1412
_LOGGER = logging.getLogger(__name__)
@@ -73,117 +71,112 @@ def make_title(title):
7371
7472
"""
7573

76-
# Update the code to compute this list automatically
77-
MULTIAPI_VERSION_NAMESPACE = [
78-
"azure.mgmt.storage",
79-
"azure.mgmt.web",
80-
"azure.mgmt.network",
81-
"azure.mgmt.compute",
82-
"azure.mgmt.containerregistry",
83-
"azure.mgmt.containerservice",
84-
"azure.mgmt.dns",
85-
"azure.mgmt.eventhub",
86-
"azure.mgmt.resource.resources",
87-
"azure.mgmt.resource.features",
88-
"azure.mgmt.resource.links",
89-
"azure.mgmt.resource.locks",
90-
"azure.mgmt.resource.policy",
91-
"azure.mgmt.resource.subscriptions",
92-
]
93-
94-
95-
def get_updated_config(config_path: str, package_root: str) -> Dict[str, Dict[str, typing.Union[str, List[str]]]]:
96-
with Path(config_path).open() as config_fd:
97-
config = json.load(config_fd)
9874

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)
75+
def get_valid_versions(api_directory: str) -> List[str]:
10476
glob_path = os.path.join(api_directory, "v20*/")
105-
valid_versions = glob.glob(glob_path)
77+
return glob.glob(glob_path)
10678

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}"
11079

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)
80+
def is_subnamespace(version: str) -> bool:
81+
return not version.startswith("v20")
11782

118-
return config
11983

84+
def get_package_namespaces(package_root: str) -> List[str]:
85+
namespace_folders = os.path.basename(package_root).split("-")
86+
namespace = ".".join(namespace_folders)
12087

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")
88+
# add top namespace
89+
namespaces = {namespace: []}
12790

128-
config = get_updated_config(config_path, package_root)
91+
api_directory = os.path.join(package_root, *namespace_folders)
92+
valid_versions = get_valid_versions(api_directory)
12993

130-
package_list_path = []
94+
# check for subnamespaces like azure.mgmt.resource.locks
95+
if not valid_versions:
96+
subnamespaces = glob.glob(f"{api_directory}/*/")
97+
for snp_path in subnamespaces:
98+
versions = get_valid_versions(snp_path)
99+
valid_versions.extend(versions)
131100

132-
namespaces = [n for pack in config.values() for n in pack.get("namespaces", {})]
101+
for version_path in valid_versions:
102+
version = os.path.relpath(version_path, start=api_directory)
103+
if is_subnamespace(version):
104+
subnamespace_name, api_version = version.split("/")
105+
full_namespace = ".".join([namespace, subnamespace_name, api_version])
106+
subnamespace = ".".join([namespace, subnamespace_name])
107+
if subnamespace not in namespaces[namespace]:
108+
namespaces[namespace].append(subnamespace)
109+
namespaces.setdefault(subnamespace, []).append(full_namespace)
110+
else:
111+
full_namespace = ".".join([namespace, version])
112+
namespaces[namespace].append(full_namespace)
133113

134-
for namespace in namespaces:
135-
if project_pattern and not namespace.startswith(project_pattern):
136-
continue
114+
return namespaces
137115

138-
_LOGGER.info("Working on %s", namespace)
139116

140-
rst_path = rst_path_template.format(namespace)
141-
with Path(rst_path).open("w") as rst_file:
142-
rst_file.write(PACKAGE_TEMPLATE.format(title=make_title(namespace + " package"), namespace=namespace))
117+
def write_rst(version: str, rst_path_template: str, rst_namespace_template: str, package_list_path: List[str]) -> None:
118+
rst_path = rst_path_template.format(version)
119+
with Path(rst_path).open("w") as rst_file:
120+
rst_file.write(PACKAGE_TEMPLATE.format(title=make_title(version + " package"), namespace=version))
143121

144-
for module in ["operations", "models"]:
145-
with Path(rst_namespace_template.format(namespace, module)).open("w") as rst_file:
146-
rst_file.write(
147-
SUBMODULE_TEMPLATE.format(
148-
title=make_title(namespace + "." + module + " module"), namespace=namespace, submodule=module
149-
)
122+
for module in ["operations", "models"]:
123+
with Path(rst_namespace_template.format(version, module)).open("w") as rst_file:
124+
rst_file.write(
125+
SUBMODULE_TEMPLATE.format(
126+
title=make_title(version + "." + module + " module"), namespace=version, submodule=module
150127
)
128+
)
129+
package_list_path.append(rst_path)
151130

152-
for multiapi_namespace in MULTIAPI_VERSION_NAMESPACE:
153-
length = len(multiapi_namespace.split("."))
154-
if namespace.split(".")[0:length] == multiapi_namespace.split(".")[0:length]:
155-
_LOGGER.info("MultiAPI namespace on %s", multiapi_namespace)
156-
api_package = namespace.split(multiapi_namespace + ".")[1]
157-
multiapi_found_apiversion.setdefault(multiapi_namespace, []).append(api_package)
158-
break
159-
else:
160-
package_list_path.append(rst_path)
161131

162-
for multiapi_namespace, apilist in multiapi_found_apiversion.items():
163-
apilist.sort()
164-
apilist.reverse()
165-
rst_path = rst_path_template.format(multiapi_namespace)
166-
with Path(rst_path).open("w") as rst_file:
167-
rst_file.write(
168-
MULTIAPI_VERSION_PACKAGE_TEMPLATE.format(
169-
title=make_title(multiapi_namespace + " package"), namespace=multiapi_namespace
170-
)
132+
def write_multiapi_rst(namespace: str, versions: List[str], rst_path_template: str, package_list_path: List[str]) -> None:
133+
rst_path = rst_path_template.format(namespace)
134+
with Path(rst_path).open("w") as rst_file:
135+
rst_file.write(
136+
MULTIAPI_VERSION_PACKAGE_TEMPLATE.format(
137+
title=make_title(namespace + " package"), namespace=namespace
171138
)
172-
for version in apilist:
173-
rst_file.write(" {namespace}.{version}\n".format(namespace=multiapi_namespace, version=version))
174-
package_list_path.append(rst_path)
139+
)
140+
for version in versions:
141+
rst_file.write(" {version}\n".format(version=version))
142+
package_list_path.append(rst_path)
175143

176-
# now handle the packages from data plane that we want to be present properly sorted in the list of packages
177-
for package in config.keys():
178-
if "manually_generated" in config[package] and config[package]["manually_generated"] == True:
179-
package_list_path.append(rst_path_template.format(package.replace("-", ".")))
180144

145+
def write_toc_tree(package_list_path: List[str]) -> None:
181146
package_list_path.sort()
182147
with Path(GENERATED_PACKAGES_LIST_FILE).open("w") as generate_file_list_fd:
183148
lines_to_write = "\n".join([" " + package for package in package_list_path])
184149
generate_file_list_fd.write(RST_AUTODOC_TOCTREE.format(generated_packages=lines_to_write))
185150

186151

152+
def generate_doc(output_directory: str = "./ref/", package_root: str = None) -> None:
153+
# we are handed a directory that looks like <path-to-repo>/sdk/containerservice/azure-mgmt-containerservice/
154+
rst_path_template = os.path.join(output_directory, "{}.rst")
155+
rst_namespace_template = os.path.join(output_directory, "{}.{}.rst")
156+
157+
namespaces = get_package_namespaces(package_root)
158+
159+
package_list_path = []
160+
161+
for namespace, multi_api_versions in namespaces.items():
162+
_LOGGER.info("Working on %s", namespace)
163+
if not multi_api_versions:
164+
write_rst(namespace, rst_path_template, rst_namespace_template, package_list_path)
165+
continue
166+
167+
multi_api_versions.sort(reverse=True)
168+
write_multiapi_rst(namespace, multi_api_versions, rst_path_template, package_list_path)
169+
170+
for version in multi_api_versions:
171+
if is_subnamespace(version.split(".")[-1]):
172+
# subnamespace already handled in write_multiapi_rst
173+
continue
174+
_LOGGER.info("Working on %s", version)
175+
write_rst(version, rst_path_template, rst_namespace_template, package_list_path)
176+
177+
write_toc_tree(package_list_path)
178+
179+
187180
def main():
188181
parser = argparse.ArgumentParser(description="Generate sphinx api stubs for one or multiple management packages.")
189182
parser.add_argument(
@@ -192,13 +185,6 @@ def main():
192185
dest="project",
193186
help="Want to target a specific management package? Pass the targeted package root to this argument.",
194187
)
195-
parser.add_argument(
196-
"--config",
197-
"-c",
198-
dest="config_path",
199-
default=CONFIG_FILE,
200-
help="The JSON configuration format path [default: %(default)s]",
201-
)
202188
parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="Verbosity in INFO mode")
203189
parser.add_argument("--debug", dest="debug", action="store_true", help="Verbosity in DEBUG mode")
204190
parser.add_argument(
@@ -212,7 +198,7 @@ def main():
212198
logging.basicConfig()
213199
main_logger.setLevel(logging.DEBUG if args.debug else logging.INFO)
214200

215-
generate_doc(args.config_path, args.output_directory, args.project)
201+
generate_doc(args.output_directory, args.project)
216202

217203

218204
if __name__ == "__main__":

0 commit comments

Comments
 (0)