Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python API review is not generated using correct package name #9954

Merged
merged 6 commits into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/python-packages/apiview-stub-generator/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Release History

## Version 0.3.15 (Unreleased)
## Version 0.3.16 (2025-03-03)
Fixed emty package name issue when running parser against pacakge source path instead of wheel. PKG_INFO is not available in this case.

## Version 0.3.15 (2025-02-28)
Fixed issue where module-level overloads were not being parsed.
Added support for parsing pyproject.toml-managed packages.
Updated RelatedToLine for empty lines to show in the diff.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ def __init__(self, **kwargs):
logging.error("Temp path [{0}] is invalid".format(temp_path))
exit(1)

if os.path.isdir(pkg_path):
pkg_path = os.path.abspath(pkg_path)
self.pkg_path = pkg_path
self.temp_path = temp_path
self.out_path = out_path
Expand Down Expand Up @@ -162,20 +164,27 @@ def install_extra_dependencies(self):
pass

def _get_pkg_metadata(self):
# pkginfo does not get package metadata in 3.10 when running against package root path
if not self.wheel_path:
pkg_root_path = self.pkg_path
pkg_name = os.path.split(self.pkg_path)[-1]
version = importlib.metadata.version(pkg_name)
dist = importlib.metadata.distribution(pkg_name)
self.extras_require = dist.metadata.get_all('Provides-Extra')
return pkg_root_path, pkg_name, version
pkg_root_path = self.wheel_path
metadata = get_metadata(self.pkg_path)
pkg_name = metadata.name
version = metadata.version
pkg_root_path = self.wheel_path or self.pkg_path
self.extras_require = metadata.provides_extras
return pkg_root_path, pkg_name, version

def generate_tokens(self):
# TODO: We should install to a virtualenv
logging.debug("Installing package from {}".format(self.pkg_path))
pkg_root_path, pkg_name, version = self._get_pkg_metadata()
self._install_package()

logging.debug(
pkg_root_path, pkg_name, version = self._get_pkg_metadata()
logging.info(
"package name: {0}, version:{1}, namespace:{2}".format(
pkg_name, version, self.namespace
)
Expand Down Expand Up @@ -228,8 +237,10 @@ def _find_modules(self, pkg_root_path):
for root, subdirs, files in os.walk(pkg_root_path):
# Ignore any modules with name starts with "_"
# For e.g. _generated, _shared etc
# Ignore build, which is created when installing a package from source.
# Ignore tests, which may have an __init__.py but is not part of the package.
dirs_to_skip = [
x for x in subdirs if x.startswith("_") or x.startswith(".")
x for x in subdirs if x.startswith("_") or x.startswith(".") or x == "tests" or x == "build"
]
for d in dirs_to_skip:
subdirs.remove(d)
Expand All @@ -240,11 +251,7 @@ def _find_modules(self, pkg_root_path):
os.path.sep, "."
)
# If namespace has not been set yet, try to find the first __init__.py that's not purely for extension or tests.
# Ignore build, which is created when installing a package from source.
# Ignore tests, which may have an __init__.py but is not part of the package.
if not self.namespace and not module_name.startswith(
("tests", "build")
):
if not self.namespace:
self._set_root_namespace(
os.path.join(root, INIT_PY_FILE), module_name
)
Expand Down Expand Up @@ -383,4 +390,4 @@ def _get_whl_root_namespace(self, wheel_extract_path):

def _install_package(self):
commands = [sys.executable, "-m", "pip", "install", self.pkg_path, "-q"]
check_call(commands, timeout=60)
check_call(commands, timeout=120)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

VERSION = "0.3.15"
VERSION = "0.3.16"
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,21 @@ def test_optional_dependencies(self, pkg_path):
self._uninstall_dep(dep)
# uninstall apistubgentest if installed, so new install will be from pkg_path
self._uninstall_dep("apistubgentest")
# if pkg is src, rm *.egg-info from path to check that pkg metadata parsing works
if os.path.isdir(pkg_path):
for f in os.listdir(pkg_path):
if f == "apistubgentest.egg-info":
shutil.rmtree(os.path.join(pkg_path, f))
break
temp_path = tempfile.gettempdir()
stub_gen = StubGenerator(pkg_path=pkg_path, temp_path=temp_path)
apiview = stub_gen.generate_tokens()
for dep in ["httpx", "pandas"]:
assert self._dependency_installed(dep)
# skip conditional optional dependencies
assert not self._dependency_installed("qsharp")
# assert package name is correct
assert apiview.package_name == "apistubgentest"

@mark.parametrize("pkg_path", PYPROJECT_PATHS, ids=PYPROJECT_IDS)
def test_pyproject_toml_line_ids(self, pkg_path):
Expand Down