Skip to content

Commit 3b60667

Browse files
authored
Add apistub Check Without Tox (#44124)
* initial * black * invoke with executable * freeze * clean and fix outpath * always use wheel * minor * minor * version check * template update * minor
1 parent 967ca1d commit 3b60667

File tree

4 files changed

+142
-0
lines changed

4 files changed

+142
-0
lines changed

doc/tool_usage_guide.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This repo is currently migrating all checks from a slower `tox`-based framework,
2222
|`black`| Runs `black` checks. | `azpysdk black .` |
2323
|`verifytypes`| Runs `verifytypes` checks. | `azpysdk verifytypes .` |
2424
|`ruff`| Runs `ruff` checks. | `azpysdk ruff .` |
25+
|`apistub`| Generates an api stub for the package. | `azpysdk apistub .` |
2526
|`bandit`| Runs `bandit` checks, which detect common security issues. | `azpysdk bandit .` |
2627
|`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 .` |
2728
|`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 .` |
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import argparse
2+
import os
3+
import sys
4+
5+
from typing import Optional, List
6+
from subprocess import CalledProcessError
7+
8+
from .Check import Check
9+
from ci_tools.functions import install_into_venv, find_whl
10+
from ci_tools.scenario.generation import create_package_and_install
11+
from ci_tools.variables import discover_repo_root, set_envvar_defaults
12+
from ci_tools.logging import logger
13+
from ci_tools.parsing import ParsedSetup
14+
15+
REPO_ROOT = discover_repo_root()
16+
MAX_PYTHON_VERSION = (3, 11)
17+
18+
19+
def get_package_wheel_path(pkg_root: str, out_path: Optional[str]) -> tuple[str, Optional[str]]:
20+
# parse setup.py to get package name and version
21+
pkg_details = ParsedSetup.from_path(pkg_root)
22+
23+
# Check if wheel is already built and available for current package
24+
prebuilt_dir = os.getenv("PREBUILT_WHEEL_DIR")
25+
out_token_path = None
26+
if prebuilt_dir:
27+
found_whl = find_whl(prebuilt_dir, pkg_details.name, pkg_details.version)
28+
pkg_path = os.path.join(prebuilt_dir, found_whl) if found_whl else None
29+
if not pkg_path:
30+
raise FileNotFoundError(
31+
"No prebuilt wheel found for package {} version {} in directory {}".format(
32+
pkg_details.name, pkg_details.version, prebuilt_dir
33+
)
34+
)
35+
# If the package is a wheel and out_path is given, the token file output path should be the parent directory of the wheel
36+
if out_path:
37+
out_token_path = os.path.join(out_path, os.path.basename(os.path.dirname(pkg_path)))
38+
return pkg_path, out_token_path
39+
40+
# Otherwise, use wheel created in staging directory, or fall back on source directory
41+
pkg_path = find_whl(pkg_root, pkg_details.name, pkg_details.version) or pkg_root
42+
out_token_path = out_path
43+
44+
return pkg_path, out_token_path
45+
46+
47+
def get_cross_language_mapping_path(pkg_root):
48+
mapping_path = os.path.join(pkg_root, "apiview-properties.json")
49+
if os.path.exists(mapping_path):
50+
return mapping_path
51+
return None
52+
53+
54+
class apistub(Check):
55+
def __init__(self) -> None:
56+
super().__init__()
57+
58+
def register(
59+
self, subparsers: "argparse._SubParsersAction", parent_parsers: Optional[List[argparse.ArgumentParser]] = None
60+
) -> None:
61+
"""Register the apistub check. The apistub check generates an API stub of the target package."""
62+
parents = parent_parsers or []
63+
p = subparsers.add_parser(
64+
"apistub", parents=parents, help="Run the apistub check to generate an API stub for a package"
65+
)
66+
p.set_defaults(func=self.run)
67+
68+
def run(self, args: argparse.Namespace) -> int:
69+
"""Run the apistub check command."""
70+
logger.info("Running apistub check...")
71+
72+
if sys.version_info > MAX_PYTHON_VERSION:
73+
logger.error(
74+
f"Python version {sys.version_info.major}.{sys.version_info.minor} is not supported. Maximum supported version is {MAX_PYTHON_VERSION[0]}.{MAX_PYTHON_VERSION[1]}."
75+
)
76+
return 1
77+
78+
set_envvar_defaults()
79+
targeted = self.get_targeted_directories(args)
80+
81+
results: List[int] = []
82+
83+
for parsed in targeted:
84+
package_dir = parsed.folder
85+
package_name = parsed.name
86+
executable, staging_directory = self.get_executable(args.isolate, args.command, sys.executable, package_dir)
87+
logger.info(f"Processing {package_name} for apistub check")
88+
89+
# install dependencies
90+
self.install_dev_reqs(executable, args, package_dir)
91+
92+
try:
93+
install_into_venv(
94+
executable,
95+
[
96+
"-r",
97+
os.path.join(REPO_ROOT, "eng", "apiview_reqs.txt"),
98+
"--index-url=https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/",
99+
],
100+
package_dir,
101+
)
102+
except CalledProcessError as e:
103+
logger.error(f"Failed to install dependencies: {e}")
104+
return e.returncode
105+
106+
create_package_and_install(
107+
distribution_directory=staging_directory,
108+
target_setup=package_dir,
109+
skip_install=True,
110+
cache_dir=None,
111+
work_dir=staging_directory,
112+
force_create=False,
113+
package_type="wheel",
114+
pre_download_disabled=False,
115+
python_executable=executable,
116+
)
117+
118+
self.pip_freeze(executable)
119+
120+
pkg_path, out_token_path = get_package_wheel_path(package_dir, staging_directory)
121+
cross_language_mapping_path = get_cross_language_mapping_path(package_dir)
122+
123+
cmds = ["-m", "apistub", "--pkg-path", pkg_path]
124+
125+
if out_token_path:
126+
cmds.extend(["--out-path", out_token_path])
127+
if cross_language_mapping_path:
128+
cmds.extend(["--mapping-path", cross_language_mapping_path])
129+
130+
logger.info("Running apistub {}.".format(cmds))
131+
132+
try:
133+
self.run_venv_command(executable, cmds, cwd=package_dir, check=True, immediately_dump=True)
134+
except CalledProcessError as e:
135+
logger.error(f"{package_name} exited with error {e.returncode}")
136+
results.append(e.returncode)
137+
138+
return max(results) if results else 0

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .next_pyright import next_pyright
2525
from .ruff import ruff
2626
from .verifytypes import verifytypes
27+
from .apistub import apistub
2728
from .verify_sdist import verify_sdist
2829
from .whl import whl
2930
from .verify_whl import verify_whl
@@ -86,6 +87,7 @@ def build_parser() -> argparse.ArgumentParser:
8687
next_pyright().register(subparsers, [common])
8788
ruff().register(subparsers, [common])
8889
verifytypes().register(subparsers, [common])
90+
apistub().register(subparsers, [common])
8991
verify_sdist().register(subparsers, [common])
9092
whl().register(subparsers, [common])
9193
verify_whl().register(subparsers, [common])

sdk/template/azure-template/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,4 @@ pyright = true
4949
pylint = true
5050
black = true
5151
generate = false
52+
apistub = false

0 commit comments

Comments
 (0)