Skip to content

Commit f1dd4bb

Browse files
authored
[azpysdk] Refactor apistub so that API consistency files are the default output (#47561)
* Refactor so api consistency files are the default output. * Remove `--dest-dir` option due to inconsistent/surprising behavior.
1 parent 53cd698 commit f1dd4bb

11 files changed

Lines changed: 97 additions & 134 deletions

File tree

.github/skills/generate-api-markdown/SKILL.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ description: Generate an API markdown file and token file using ApiView. Use thi
1919
1. Navigate to the desired package directory
2020
2. Run the command:
2121
```bash
22-
azpysdk apistub --md --extract-metadata --install-deps --dest-dir . .
22+
azpysdk apistub .
2323
```
24-
3. The command outputs the location of the generated markdown file. Provide this file to the user for review.
24+
3. The command generates `api.md` and `api.metadata.yml` in the package directory, which are the files needed to pass the API consistency check. Provide these files to the user for review.
25+
4. If the user explicitly asks for the raw APIView token file, run `azpysdk apistub . --token-file` instead.

.github/workflows/src/api-md-consistency/adapters/python.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ function generateApiForPackage({
131131
const pythonExecutable = runtimeExecutable || process.env.RUNTIME_EXECUTABLE;
132132
run(
133133
pythonExecutable,
134-
["-m", "azpysdk.main", "apistub", "--md", "--extract-metadata", "--dest-dir", packageDir, packageName],
134+
["-m", "azpysdk.main", "apistub", packageName],
135135
{
136136
cwd: repoRoot,
137137
check: true,
@@ -141,7 +141,7 @@ function generateApiForPackage({
141141
return;
142142
}
143143

144-
run("azpysdk", ["apistub", "--md", "--extract-metadata", "--dest-dir", packageDir, packageName], {
144+
run("azpysdk", ["apistub", packageName], {
145145
cwd: repoRoot,
146146
check: true,
147147
logger: activeLogger,

.github/workflows/src/api-md-consistency/api-md-consistency.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ function formatIssueSection(title, apiFiles) {
4747
lines.push(styleLog(`PACKAGE: ${packageName}`, ANSI.bold, ANSI.cyan));
4848
lines.push(`PATH: ${packageDir}`);
4949
lines.push(`API FILE: ${apiFile}`);
50-
lines.push(styleLog("Regenerate from the repository root:", ANSI.bold, ANSI.yellow));
51-
lines.push(styleLog(` azpysdk apistub --md --extract-metadata ${packageName} --dest-dir .`, ANSI.bold, ANSI.yellow));
50+
lines.push(styleLog(`Regenerate from the ${packageName} package root:`, ANSI.bold, ANSI.yellow));
51+
lines.push(styleLog(` azpysdk apistub .`, ANSI.bold, ANSI.yellow));
5252
lines.push("============================================================");
5353
}
5454
lines.push("");
@@ -97,7 +97,7 @@ export default async function apiMdConsistency({ core }) {
9797
"",
9898
formatIssueSection("Mismatched packages:", mismatches),
9999
formatIssueSection("Missing required API files:", missing),
100-
"To regenerate api.md locally, run the command shown for each package from the repository root.",
100+
"To regenerate api.md and api.metadata.yml locally, run the command shown for each package from the repository root.",
101101
].filter((part) => part !== "");
102102

103103
core.setFailed(messageParts.join("\n"));

doc/dev/tests.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ A quick description of the commands above:
147147
- verifywhl: verifies the wheel contents and manifest
148148
- verifysdist: verifies the sdist contents and manifest
149149
- samples: runs all of the samples in the `samples` directory and verifies they are working correctly
150-
- apistub: runs the [apistubgenerator](https://github.com/Azure/azure-sdk-tools/tree/main/packages/python-packages/apiview-stub-generator) tool on your code
150+
- apistub: runs the [apistubgenerator](https://github.com/Azure/azure-sdk-tools/tree/main/packages/python-packages/apiview-stub-generator) tool on your code. By default, generates `api.md` and `api.metadata.yml` in the package directory, which are the files needed to pass the API consistency check. Use `--token-file` to generate only the raw APIView token file.
151151

152152
## The `devtools_testutils` package
153153

doc/tool_usage_guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The following checks are available via the `azpysdk` entrypoint.
2121
|`pyright`| Runs `pyright` checks or `next-pyright` checks. (based on presence of `--next` argument) | `azpysdk pyright .` |
2222
|`black`| Runs `black` checks. | `azpysdk black .` |
2323
|`verifytypes`| Runs `verifytypes` checks. | `azpysdk verifytypes .` |
24-
|`apistub`| Generates an api stub for the package. | `azpysdk apistub .` |
24+
|`apistub`| Generates `api.md`, API metadata, or APIView token files for the package. | `azpysdk apistub .` |
2525
|`bandit`| Runs `bandit` checks, which detect common security issues. | `azpysdk bandit .` |
2626
|`verifywhl`| Verifies that the root directory in whl is azure, and verifies manifest so that all directories in source are included in sdist. | `azpysdk verifywhl .` |
2727
|`verifysdist`| Verify directories included in sdist and contents in manifest file. Also ensures that py.typed configuration is correct within the setup.py. | `azpysdk verifysdist .` |

eng/tools/azure-sdk-tools/azpysdk/apistub.py

Lines changed: 25 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,11 @@ def register(
5757
"apistub", parents=parents, help="Run the apistub check to generate an API stub for a package"
5858
)
5959
p.add_argument(
60-
"--dest-dir",
61-
dest="dest_dir",
62-
default=None,
63-
help="Destination directory for generated API stub token files.",
64-
)
65-
p.add_argument(
66-
"--md",
67-
dest="generate_md",
68-
default=False,
69-
action="store_true",
70-
help="Generate api.md from the JSON token file using Export-APIViewMarkdown.ps1. Output directory for api.md is the same as the generated token file.",
71-
)
72-
p.add_argument(
73-
"--extract-metadata",
74-
dest="extract_metadata",
60+
"--token-file",
61+
dest="token_file",
7562
default=False,
7663
action="store_true",
77-
help="Extract language-specific metadata from generated api.md into api.metadata.yml and remove metadata header from api.md.",
64+
help="Generate only the raw APIView token file.",
7865
)
7966
p.add_argument(
8067
"--install-deps",
@@ -106,9 +93,8 @@ def run(self, args: argparse.Namespace) -> int:
10693
"""Run the apistub check command."""
10794
logger.info("Running apistub check...")
10895

109-
if getattr(args, "extract_metadata", False) and not getattr(args, "generate_md", False):
110-
logger.error("--extract-metadata requires --md.")
111-
return 1
96+
token_file = getattr(args, "token_file", False)
97+
generate_markdown = not token_file
11298

11399
set_envvar_defaults()
114100
targeted = self.get_targeted_directories(args)
@@ -160,12 +146,8 @@ def run(self, args: argparse.Namespace) -> int:
160146
pkg_path = get_package_wheel_path(package_dir)
161147
pkg_path = os.path.abspath(pkg_path)
162148

163-
dest_dir = getattr(args, "dest_dir", None)
164-
if dest_dir:
165-
out_token_path = os.path.abspath(dest_dir)
166-
os.makedirs(out_token_path, exist_ok=True)
167-
else:
168-
out_token_path = os.path.abspath(staging_directory)
149+
out_token_path = os.path.abspath(package_dir)
150+
os.makedirs(out_token_path, exist_ok=True)
169151

170152
cross_language_mapping_path = get_cross_language_mapping_path(package_dir)
171153

@@ -178,15 +160,21 @@ def run(self, args: argparse.Namespace) -> int:
178160
cmds.extend(["--out-path", out_token_path])
179161
if cross_language_mapping_path:
180162
cmds.extend(["--mapping-path", cross_language_mapping_path])
181-
if getattr(args, "generate_md", False):
163+
if generate_markdown:
182164
cmds.append("--skip-pylint")
183165

184166
logger.info("Running apistub {}.".format(cmds))
185167

186168
try:
187169
self.run_venv_command(executable, cmds, cwd=staging_directory, check=True, immediately_dump=True)
188-
if getattr(args, "generate_md", False):
189-
token_json_path = os.path.join(out_token_path, f"{package_name}_python.json")
170+
token_json_path = os.path.join(out_token_path, f"{package_name}_python.json")
171+
if token_file:
172+
if os.path.exists(token_json_path):
173+
logger.info(f"Generated APIView token file: {token_json_path}")
174+
else:
175+
logger.error(f"Expected APIView token file was not generated: {token_json_path}")
176+
results.append(1)
177+
else:
190178
md_script = os.path.join(REPO_ROOT, "eng", "common", "scripts", "Export-APIViewMarkdown.ps1")
191179
metadata_script = os.path.join(REPO_ROOT, "eng", "scripts", "Extract-APIViewMetadata-Python.ps1")
192180
logger.info(f"Generating api.md for {package_name}")
@@ -201,16 +189,15 @@ def run(self, args: argparse.Namespace) -> int:
201189
if result.stdout:
202190
logger.info(result.stdout)
203191

204-
if getattr(args, "extract_metadata", False):
205-
logger.info(f"Extracting API metadata for {package_name}")
206-
metadata_result = run(
207-
["pwsh", metadata_script, "-OutputPath", out_token_path],
208-
check=True,
209-
capture_output=True,
210-
text=True,
211-
)
212-
if metadata_result.stdout:
213-
logger.info(metadata_result.stdout)
192+
logger.info(f"Extracting API metadata for {package_name}")
193+
metadata_result = run(
194+
["pwsh", metadata_script, "-OutputPath", out_token_path],
195+
check=True,
196+
capture_output=True,
197+
text=True,
198+
)
199+
if metadata_result.stdout:
200+
logger.info(metadata_result.stdout)
214201
except FileNotFoundError:
215202
logger.error("Failed to generate api.md: pwsh (PowerShell) is not installed or not on PATH.")
216203
results.append(1)

eng/tools/azure-sdk-tools/ci_tools/functions.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,19 @@ def glob_packages(glob_string: str, target_root_dir: str) -> List[str]:
182182
collected_top_level_directories = []
183183

184184
for glob_string in individual_globs:
185-
globbed = glob.glob(os.path.join(target_root_dir, glob_string, "setup.py"), recursive=True) + glob.glob(
186-
os.path.join(target_root_dir, "sdk/*/", glob_string, "setup.py")
185+
globbed = (
186+
glob.glob(os.path.join(target_root_dir, glob_string, "setup.py"), recursive=True)
187+
+ glob.glob(os.path.join(target_root_dir, "*/", glob_string, "setup.py"))
188+
+ glob.glob(os.path.join(target_root_dir, "sdk/*/", glob_string, "setup.py"))
187189
)
188190
collected_top_level_directories.extend([os.path.dirname(p) for p in globbed])
189191

190192
# handle pyproject.toml separately, as we need to filter them by the presence of a `[project]` section
191193
for glob_string in individual_globs:
192-
globbed = glob.glob(os.path.join(target_root_dir, glob_string, "pyproject.toml"), recursive=True) + glob.glob(
193-
os.path.join(target_root_dir, "sdk/*/", glob_string, "pyproject.toml")
194+
globbed = (
195+
glob.glob(os.path.join(target_root_dir, glob_string, "pyproject.toml"), recursive=True)
196+
+ glob.glob(os.path.join(target_root_dir, "*/", glob_string, "pyproject.toml"))
197+
+ glob.glob(os.path.join(target_root_dir, "sdk/*/", glob_string, "pyproject.toml"))
194198
)
195199
for p in globbed:
196200
if get_pyproject(os.path.dirname(p)):

eng/tools/azure-sdk-tools/packaging_tools/sdk_generator.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -335,11 +335,7 @@ def main(generate_input, generate_output):
335335
cmds = [
336336
"azpysdk",
337337
"apistub",
338-
"--md",
339-
"--extract-metadata",
340338
package_name,
341-
"--dest-dir",
342-
package_path.absolute().as_posix(),
343339
]
344340
_LOGGER.info(f"generate apiview file for package {package_name}")
345341
check_call(

eng/tools/azure-sdk-tools/tests/integration/test_package_discovery.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from ci_tools.parsing import ParsedSetup
44
from ci_tools.functions import discover_targeted_packages
55

6-
76
repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "..", ".."))
87
sdk_root = os.path.join(repo_root, "sdk")
98
core_service_root = os.path.join(sdk_root, "core")
@@ -70,6 +69,22 @@ def test_discovery_single_package():
7069
]
7170

7271

72+
def test_discovery_single_package_from_sdk_root():
73+
results = discover_targeted_packages("azure-template", sdk_root, filter_type="Build")
74+
75+
assert [os.path.basename(result) for result in results] == [
76+
"azure-template",
77+
]
78+
79+
80+
def test_discovery_single_package_from_repo_root():
81+
results = discover_targeted_packages("azure-template", repo_root, filter_type="Build")
82+
83+
assert [os.path.basename(result) for result in results] == [
84+
"azure-template",
85+
]
86+
87+
7388
def test_discovery_omit_regression():
7489
results = discover_targeted_packages("*", core_service_root, filter_type="Regression")
7590

0 commit comments

Comments
 (0)