Skip to content
Draft
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
51 changes: 47 additions & 4 deletions eng/tools/azure-sdk-tools/azpysdk/verify_whl.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import argparse
import logging
import os
import sys
import glob
Expand Down Expand Up @@ -77,7 +76,7 @@ def verify_whl_root_directory(
non_azure_folders = [d for d in root_folders if d != expected_top_level_module and not d.endswith(".dist-info")]

if non_azure_folders:
logging.error(
logger.error(
"whl has following incorrect directory at root level [%s]",
non_azure_folders,
)
Expand All @@ -99,6 +98,43 @@ def should_verify_package(package_name):
return package_name not in EXCLUDED_PACKAGES and "nspkg" not in package_name and "-mgmt" not in package_name


def has_stable_version_on_pypi(package_name: str) -> bool:
"""Check if the package has any stable (non-prerelease) version on PyPI."""
try:
all_versions = retrieve_versions_from_pypi(package_name)
stable_versions = [Version(v) for v in all_versions if not Version(v).is_prerelease]
return len(stable_versions) > 0
except Exception:
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to verify_conda_section, the bare Exception catch here is too broad and could hide bugs. The function returns False for any exception (including network errors, JSON parsing errors, etc.), which makes debugging difficult. Consider either catching specific exceptions or logging the exception before returning False so that failures are visible in CI logs.

Suggested change
except Exception:
except Exception as exc:
logger.error("Failed to retrieve stable versions for %s from PyPI: %s", package_name, exc)

Copilot uses AI. Check for mistakes.
return False


def verify_conda_section(package_dir: str, package_name: str) -> bool:
"""Verify that packages with stable versions on PyPI have [tool.azure-sdk-conda] section in pyproject.toml."""
if not has_stable_version_on_pypi(package_name):
logger.info(f"Package {package_name} has no stable version on PyPI, skipping conda section check")
return True

pyproject_path = os.path.join(package_dir, "pyproject.toml")
if not os.path.exists(pyproject_path):
logger.error(f"Package {package_name} has a stable version on PyPI but is missing pyproject.toml")
return False

try:
with open(pyproject_path, "r", encoding="utf-8") as f:
content = f.read()

if "[tool.azure-sdk-conda]" not in content:
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string search for "[tool.azure-sdk-conda]" may produce false positives if this string appears in comments or string literals within the pyproject.toml file. Consider parsing the TOML file properly to check for the actual section. The codebase already uses tomllib/tomli for TOML parsing (see ci_tools/parsing/parse_functions.py), so you could load the file and check if "azure-sdk-conda" exists in toml_dict.get("tool", {}).

Copilot uses AI. Check for mistakes.
logger.error(
f"Package {package_name} has a stable version on PyPI but is missing "
"[tool.azure-sdk-conda] section in pyproject.toml"
)
return False
return True
Comment on lines +126 to +132
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verification only checks for the presence of the [tool.azure-sdk-conda] section but doesn't validate that it contains the required fields. Based on existing pyproject.toml files (e.g., sdk/core/azure-core/pyproject.toml), packages should specify "in_bundle" and "bundle_name" fields. Consider validating that these required fields are present and properly configured to provide more helpful feedback to service teams.

Copilot uses AI. Check for mistakes.
except Exception as e:
logger.error(f"Failed to read pyproject.toml for {package_name}: {e}")
return False
Comment on lines +101 to +135
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new verify_conda_section and has_stable_version_on_pypi functions lack test coverage. The repository has comprehensive automated testing for verify_whl (see tests/test_metadata_verification.py), so these new functions should have corresponding tests. Consider adding test cases for: 1) packages with stable versions that have the conda section, 2) packages with stable versions missing the conda section, 3) packages without stable versions (should skip check), 4) packages missing pyproject.toml, and 5) error handling in has_stable_version_on_pypi.

Copilot uses AI. Check for mistakes.
Comment on lines +133 to +135
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bare Exception catch in the try-except block is too broad. It catches all exceptions including KeyboardInterrupt and SystemExit, which could mask serious issues. Consider catching specific exceptions like OSError or IOError for file operations, or at minimum use "except Exception as e" and re-raise after logging if it's not a recoverable error. The current implementation silently returns False for any exception, which could hide bugs.

Copilot uses AI. Check for mistakes.


def get_prior_version(package_name: str, current_version: str) -> Optional[str]:
"""Get prior stable version if it exists, otherwise get prior preview version, else return None."""
try:
Expand Down Expand Up @@ -179,7 +215,7 @@ def verify_metadata_compatibility(current_metadata: Dict[str, Any], prior_metada
repo_urls = ["homepage", "repository"]
current_keys_lower = {k.lower() for k in current_metadata.keys()}
if not any(key in current_keys_lower for key in repo_urls):
logging.error(f"Current metadata must contain at least one of: {repo_urls}")
logger.error(f"Current metadata must contain at least one of: {repo_urls}")
return False

if not prior_metadata:
Expand All @@ -192,7 +228,7 @@ def verify_metadata_compatibility(current_metadata: Dict[str, Any], prior_metada
is_compatible = prior_keys_filtered.issubset(current_keys)
if not is_compatible:
missing_keys = prior_keys_filtered - current_keys
logging.error("Metadata compatibility failed. Missing keys: %s", missing_keys)
logger.error("Metadata compatibility failed. Missing keys: %s", missing_keys)
return is_compatible


Expand Down Expand Up @@ -255,4 +291,11 @@ def run(self, args: argparse.Namespace) -> int:
logger.error(f"Failed to verify whl for package {package_name}")
results.append(1)

# Verify conda section for packages with stable versions on PyPI
if not verify_conda_section(package_dir, package_name):
logger.error(
"As part of releasing stable packages to Conda, the pyproject.toml must include a [tool.azure-sdk-conda] section and specify if the package should be released individually or bundled."
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message duplicates information already provided by the logger.error call on lines 127-130. The message on line 297 should be removed or modified to add new information, such as a link to documentation on how to configure the conda section or an example configuration.

Suggested change
"As part of releasing stable packages to Conda, the pyproject.toml must include a [tool.azure-sdk-conda] section and specify if the package should be released individually or bundled."
"As part of releasing stable packages to Conda, the pyproject.toml must include a [tool.azure-sdk-conda] "
"section and specify if the package should be released individually or bundled. For configuration guidance, "
"see https://aka.ms/azsdk/python/conda-config, for example:\n"
"[tool.azure-sdk-conda]\n"
'publish = "individual" # or "bundle"\n'
'bundle_group = "azure-ai" # optional, when using bundle'

Copilot uses AI. Check for mistakes.
)
results.append(1)

return max(results) if results else 0
Loading