diff --git a/eng/pipelines/templates/jobs/ci.tests.yml b/eng/pipelines/templates/jobs/ci.tests.yml index 7793d636ba66..48d012fe01e9 100644 --- a/eng/pipelines/templates/jobs/ci.tests.yml +++ b/eng/pipelines/templates/jobs/ci.tests.yml @@ -96,10 +96,6 @@ jobs: Paths: - '**' - # Authenticate to Azure Artifacts feed immediately after checkout to prevent 401 errors - # Public feeds have upstream sources enabled and require authentication for passthrough to pypi.org - - template: /eng/pipelines/templates/steps/auth-dev-feed.yml - - template: /eng/pipelines/templates/steps/download-package-artifacts.yml - template: /eng/pipelines/templates/steps/resolve-package-targeting.yml diff --git a/eng/pipelines/templates/jobs/ci.yml b/eng/pipelines/templates/jobs/ci.yml index 61d063daa808..ccb16f34440e 100644 --- a/eng/pipelines/templates/jobs/ci.yml +++ b/eng/pipelines/templates/jobs/ci.yml @@ -285,6 +285,9 @@ jobs: inputs: versionSpec: '3.12' - template: /eng/pipelines/templates/steps/use-venv.yml + - template: ../steps/auth-dev-feed.yml + parameters: + DevFeedName: ${{ parameters.DevFeedName }} - template: /eng/common/pipelines/templates/steps/save-package-properties.yml parameters: ServiceDirectory: ${{parameters.ServiceDirectory}} @@ -323,6 +326,11 @@ jobs: inputs: versionSpec: '3.12' - template: /eng/pipelines/templates/steps/use-venv.yml + + - template: ../steps/auth-dev-feed.yml + parameters: + DevFeedName: ${{ parameters.DevFeedName }} + - pwsh: | $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true diff --git a/eng/pipelines/templates/steps/auth-dev-feed.yml b/eng/pipelines/templates/steps/auth-dev-feed.yml index 00556968468f..c3d74eef0a4f 100644 --- a/eng/pipelines/templates/steps/auth-dev-feed.yml +++ b/eng/pipelines/templates/steps/auth-dev-feed.yml @@ -25,17 +25,17 @@ steps: artifactFeeds: $(DevFeedName) onlyAddExtraIndex: false - # Disabled UV authentication step - for future enablement when UV fully supports Azure Artifacts - # To enable: uncomment this step and ensure UV_INDEX_URL is properly configured - # The PipAuthenticate task above creates a .pypirc file in the home directory that UV can use - pwsh: | - # This step configures UV to use the same authentication as pip - # UV will read credentials from ~/.pypirc created by PipAuthenticate task if ($env:PIP_INDEX_URL) { - Write-Host "PIP Index URL detected: $env:PIP_INDEX_URL" - # Uncomment the next line to enable UV authentication - # Write-Host "##vso[task.setvariable variable=UV_INDEX_URL]$($env:PIP_INDEX_URL)" - Write-Host "UV authentication is currently disabled. To enable, uncomment the variable assignment above." + # UV_DEFAULT_INDEX is the canonical replacement for the deprecated UV_INDEX_URL (uv 0.4.23+). + # PIP_INDEX_URL is set by PipAuthenticate@1 and contains embedded credentials, which uv + # will use for Basic auth against the ADO feed (and its PyPI upstream) per astral-sh/uv#12651. + Write-Host "##vso[task.setvariable variable=UV_DEFAULT_INDEX]azure-sdk=https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/" + # Disable keyring so uv uses the URL-embedded credentials directly. + Write-Host "##vso[task.setvariable variable=UV_KEYRING_PROVIDER]disabled" + Write-Host "##vso[task.setvariable variable=UV_INDEX_AZURE_SDK_USERNAME]x" + Write-Host "##vso[task.setvariable variable=UV_INDEX_AZURE_SDK_PASSWORD]$(System.AccessToken)" + } else { + Write-Host "##[warning]PIP_INDEX_URL not set - uv will fall back to public PyPI." } - displayName: 'Configure UV Authentication (Disabled)' - condition: false # Explicitly disabled - change to 'true' or remove to enable + displayName: 'Configure UV Authentication' diff --git a/eng/pipelines/templates/steps/build-test.yml b/eng/pipelines/templates/steps/build-test.yml index 444830268f2c..70ca1a534861 100644 --- a/eng/pipelines/templates/steps/build-test.yml +++ b/eng/pipelines/templates/steps/build-test.yml @@ -28,6 +28,12 @@ steps: - template: /eng/pipelines/templates/steps/use-venv.yml + # Authenticate to Azure Artifacts feed immediately after checkout to prevent 401 errors + # Public feeds have upstream sources enabled and require authentication for passthrough to pypi.org + - template: /eng/pipelines/templates/steps/auth-dev-feed.yml + parameters: + DevFeedName: ${{ parameters.DevFeedName }} + - template: /eng/common/pipelines/templates/steps/set-test-pipeline-version.yml parameters: PackageName: "azure-template" diff --git a/eng/scripts/Language-Settings.ps1 b/eng/scripts/Language-Settings.ps1 index 3ee8871cf958..f1bc26a31b4e 100644 --- a/eng/scripts/Language-Settings.ps1 +++ b/eng/scripts/Language-Settings.ps1 @@ -166,7 +166,8 @@ function Get-AllPackageInfoFromRepo ($serviceDirectory) # Use ‘uv pip install’ if uv is on PATH, otherwise fall back to python -m pip if (Get-Command uv -ErrorAction SilentlyContinue) { Write-Host "Using uv pip install" - $null = uv pip install "$pathToBuild" + $installerOutput = uv pip install "$pathToBuild" + Write-Host $installerOutput $freezeOutput = uv pip freeze Write-Host "Pip freeze output: $freezeOutput" } else { diff --git a/eng/tools/azure-sdk-tools/ci_tools/scenario/dependency_resolution.py b/eng/tools/azure-sdk-tools/ci_tools/scenario/dependency_resolution.py index 7e357aee1b41..1e6a96720547 100644 --- a/eng/tools/azure-sdk-tools/ci_tools/scenario/dependency_resolution.py +++ b/eng/tools/azure-sdk-tools/ci_tools/scenario/dependency_resolution.py @@ -86,7 +86,7 @@ }, } -PLATFORM_SPECIFIC_MAXIMUM_OVERRIDES = {} +PLATFORM_SPECIFIC_MAXIMUM_OVERRIDES = {"<3.10.0": {"requests": "2.32.5"}} # This is used to actively _add_ requirements to the install set. These are used to actively inject # a new requirement specifier to the set of packages being installed. diff --git a/eng/tools/azure-sdk-tools/pypi_tools/azdo.py b/eng/tools/azure-sdk-tools/pypi_tools/azdo.py new file mode 100644 index 000000000000..608a2ffd05fa --- /dev/null +++ b/eng/tools/azure-sdk-tools/pypi_tools/azdo.py @@ -0,0 +1,157 @@ +import base64 +import json +import logging +import os +import re +from dataclasses import dataclass +from typing import Any, Dict, List, Optional +from urllib.parse import urlparse + +from packaging.version import Version, InvalidVersion, parse +from urllib3 import PoolManager, Retry + + +def pep503_normalize(name: str) -> str: + return re.sub(r"[-_.]+", "-", name).lower() + + +@dataclass(frozen=True) +class AzureArtifactsFeedConfig: + organization: str + project: Optional[str] # None if the feed is organization-scoped + feed: str # feed name or GUID + api_version: str = "7.1" + + bearer_token: Optional[str] = None + pat: Optional[str] = None + + +# Pattern: https://pkgs.dev.azure.com/{org}/{project}/_packaging/{feed}/pypi/simple/ +# or org-scoped: https://pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/ +_AZDO_FEED_RE = re.compile( + r"/(?P[^/]+)/(?:(?P[^/_][^/]*)/)?" r"_packaging/(?P[^/]+)/pypi/simple/?$" +) + + +def parse_pip_index_url(url: str) -> Optional[AzureArtifactsFeedConfig]: + """If *url* points to an Azure Artifacts PyPI feed, return a config; else None.""" + parsed = urlparse(url) + if "pkgs.dev.azure.com" not in parsed.hostname: + return None + + m = _AZDO_FEED_RE.search(parsed.path) + if not m: + return None + + # Embedded credentials from PipAuthenticate@1 + pat = None + if parsed.password: + pat = parsed.password + + return AzureArtifactsFeedConfig( + organization=m.group("org"), + project=m.group("project"), + feed=m.group("feed"), + pat=pat or os.environ.get("AZDO_PAT"), + ) + + +class AzureArtifactsClient: + """ + Minimal client to list package versions from an Azure Artifacts feed + via Azure DevOps Artifacts REST API. + """ + + def __init__(self, cfg: AzureArtifactsFeedConfig, base_url: str = "https://feeds.dev.azure.com"): + self._cfg = cfg + self._base_url = base_url.rstrip("/") + self._http = PoolManager( + retries=Retry(total=3, raise_on_status=True), + ca_certs=os.getenv("REQUESTS_CA_BUNDLE", None), + ) + + def _auth_header(self) -> Dict[str, str]: + if self._cfg.bearer_token: + return {"Authorization": f"Bearer {self._cfg.bearer_token}"} + + if self._cfg.pat: + # Azure DevOps PATs can be used via HTTP Basic by base64-encoding ":". + token = base64.b64encode(f":{self._cfg.pat}".encode("utf-8")).decode("ascii") + return {"Authorization": f"Basic {token}"} + + return {} + + def _path_prefix(self) -> str: + # If project-scoped feed: /{org}/{project}/... + # If org-scoped feed: /{org}/... + if self._cfg.project: + return f"{self._cfg.organization}/{self._cfg.project}" + return self._cfg.organization + + def _get_json(self, url: str, params: Dict[str, Any]) -> Any: + headers = {"Accept": "application/json", **self._auth_header()} + r = self._http.request("GET", url, fields=params, headers=headers) + return json.loads(r.data.decode("utf-8")) + + def list_feeds(self) -> List[Dict[str, Any]]: + url = f"{self._base_url}/{self._path_prefix()}/_apis/packaging/feeds" + data = self._get_json(url, {"api-version": self._cfg.api_version}) + # Many Azure DevOps APIs return {"count": n, "value": [...]}; be tolerant. + return data["value"] if isinstance(data, dict) and "value" in data else data + + def resolve_feed_id(self) -> str: + feed = self._cfg.feed + if re.fullmatch(r"[0-9a-fA-F-]{36}", feed): + return feed + + for f in self.list_feeds(): + if f.get("name") == feed: + return f["id"] + + raise KeyError(f"Feed not found: {feed!r}") + + def get_package_record(self, package_name: str, include_deleted: bool = False) -> Dict[str, Any]: + feed_id = self.resolve_feed_id() + url = f"{self._base_url}/{self._path_prefix()}/_apis/packaging/Feeds/{feed_id}/packages" + + params = { + "api-version": self._cfg.api_version, + "protocolType": "pypi", + "packageNameQuery": package_name, + "includeAllVersions": "true", + "includeDeleted": "true" if include_deleted else "false", + } + + data = self._get_json(url, params) + packages = data["value"] if isinstance(data, dict) and "value" in data else data + + # packageNameQuery is "contains string", so choose best match. + target = pep503_normalize(package_name) + for pkg in packages: + if pep503_normalize(pkg.get("normalizedName", pkg.get("name", ""))) == target: + return pkg + for pkg in packages: + if pep503_normalize(pkg.get("name", "")) == target: + return pkg + + raise KeyError(f"Package not found in feed: {package_name!r}") + + def get_ordered_versions(self, package_name: str, include_deleted: bool = False) -> List[Version]: + pkg = self.get_package_record(package_name, include_deleted=include_deleted) + + out: List[Version] = [] + for v in pkg.get("versions", []): + if (not include_deleted) and v.get("isDeleted", False): + continue + + raw = v.get("version") + if not raw: + continue + + try: + out.append(parse(raw)) + except InvalidVersion: + logging.warning("Invalid version %r for package %s (feed=%s)", raw, package_name, self._cfg.feed) + + out.sort() + return out diff --git a/eng/tools/azure-sdk-tools/pypi_tools/pypi.py b/eng/tools/azure-sdk-tools/pypi_tools/pypi.py index b373029ff06e..d7fc7fd03ffc 100644 --- a/eng/tools/azure-sdk-tools/pypi_tools/pypi.py +++ b/eng/tools/azure-sdk-tools/pypi_tools/pypi.py @@ -1,7 +1,6 @@ import logging from packaging.version import InvalidVersion, Version, parse import sys -import pdb from urllib3 import Retry, PoolManager import json import os @@ -16,33 +15,75 @@ def get_pypi_xmlrpc_client(): class PyPIClient: + """Unified package-index client. + + By default, reads ``PIP_INDEX_URL`` to decide the backend: + * If the URL contains ``pkgs.dev.azure.com`` → Azure Artifacts REST API. + * Otherwise → PyPI JSON API (``https://pypi.org``). + """ + def __init__(self, host="https://pypi.org"): - self._host = host - self._http = PoolManager( - retries=Retry(total=3, raise_on_status=True), ca_certs=os.getenv("REQUESTS_CA_BUNDLE", None) - ) + index_url = os.environ.get("PIP_INDEX_URL", "") + + # Lazy import to avoid circular deps at module level. + from pypi_tools.azdo import parse_pip_index_url, AzureArtifactsClient + + azdo_cfg = parse_pip_index_url(index_url) if index_url else None + + if azdo_cfg is not None: + self._backend = "azdo" + self._azdo = AzureArtifactsClient(azdo_cfg) + else: + self._backend = "pypi" + self._host = host + self._http = PoolManager( + retries=Retry(total=3, raise_on_status=True), + ca_certs=os.getenv("REQUESTS_CA_BUNDLE", None), + ) + + def _pypi_http(self): + """Lazy PoolManager for pypi.org fallback when on AzDO backend.""" + if not hasattr(self, "_pypi_http_pool"): + self._pypi_http_pool = PoolManager( + retries=Retry(total=3, raise_on_status=True), + ca_certs=os.getenv("REQUESTS_CA_BUNDLE", None), + ) + return self._pypi_http_pool + + def _pypi_json_request(self, path): + """GET from pypi.org JSON API, using the active backend's http pool if on pypi, else fallback.""" + if self._backend == "pypi": + url = "{host}{path}".format(host=self._host, path=path) + response = self._http.request("get", url) + else: + url = "https://pypi.org{path}".format(path=path) + response = self._pypi_http().request("get", url) + return json.loads(response.data.decode("utf-8")) + + # ------------------------------------------------------------------ + # PyPI JSON endpoints (fall back to pypi.org when on AzDO backend) + # ------------------------------------------------------------------ def project(self, package_name): - response = self._http.request( - "get", "{host}/pypi/{project_name}/json".format(host=self._host, project_name=package_name) - ) - return json.loads(response.data.decode("utf-8")) + if self._backend != "pypi": + raise NotImplementedError("project() is only available against pypi.org") + return self._pypi_json_request("/pypi/{}/json".format(package_name)) def project_release(self, package_name, version): - response = self._http.request( - "get", - "{host}/pypi/{project_name}/{version}/json".format( - host=self._host, project_name=package_name, version=version - ), - ) - return json.loads(response.data.decode("utf-8")) + return self._pypi_json_request("/pypi/{}/{}/json".format(package_name, version)) + + # ------------------------------------------------------------------ + # Shared interface + # ------------------------------------------------------------------ def filter_packages_for_compatibility(self, package_name, version_set): - # only need the packaging.specifiers import if we're actually executing this filter. + if self._backend != "pypi": + raise NotImplementedError( + "filter_packages_for_compatibility() requires pypi.org (needs requires_python metadata)" + ) from packaging.specifiers import InvalidSpecifier, SpecifierSet results: List[Version] = [] - for version in version_set: requires_python = self.project_release(package_name, version)["info"]["requires_python"] if requires_python: @@ -54,20 +95,25 @@ def filter_packages_for_compatibility(self, package_name, version_set): continue else: results.append(version) - return results def get_ordered_versions(self, package_name, filter_by_compatibility=False) -> List[Version]: - project = self.project(package_name) + if self._backend == "azdo": + versions = self._azdo.get_ordered_versions(package_name) + if filter_by_compatibility: + logging.warning( + "filter_by_compatibility is not supported against Azure Artifacts; returning unfiltered versions" + ) + return versions + project = self.project(package_name) versions: List[Version] = [] for package_version, files in project["releases"].items(): try: - # Skip yanked versions (no files or all files yanked) if not files or all(f.get("yanked", False) for f in files): continue versions.append(parse(package_version)) - except InvalidVersion as e: + except InvalidVersion: logging.warn(f"Invalid version {package_version} for package {package_name}") continue versions.sort() @@ -78,9 +124,7 @@ def get_ordered_versions(self, package_name, filter_by_compatibility=False) -> L return versions def get_relevant_versions(self, package_name): - """Return a tuple: (latest release, latest stable) - If there are different, it means the latest is not a stable - """ + """Return a tuple: (latest release, latest stable)""" versions = self.get_ordered_versions(package_name) stable_releases = [version for version in versions if not version.is_prerelease] return (versions[-1], stable_releases[-1]) @@ -88,7 +132,7 @@ def get_relevant_versions(self, package_name): def retrieve_versions_from_pypi(package_name: str) -> List[str]: """ - Retrieve all published versions on PyPI for the package. + Retrieve all published versions for the package from the active index. :param str package_name: The name of the package. :rtype: List[str] @@ -97,8 +141,7 @@ def retrieve_versions_from_pypi(package_name: str) -> List[str]: try: client = PyPIClient() all_versions = client.get_ordered_versions(package_name) - # Return all versions as strings return [str(v) for v in all_versions] except Exception as ex: - logging.warning("Failed to retrieve PyPI data for %s: %s", package_name, ex) + logging.warning("Failed to retrieve package data for %s: %s", package_name, ex) return [] diff --git a/eng/tools/azure-sdk-tools/pyproject.toml b/eng/tools/azure-sdk-tools/pyproject.toml index 0d97d4835e3e..acb9eabea8d0 100644 --- a/eng/tools/azure-sdk-tools/pyproject.toml +++ b/eng/tools/azure-sdk-tools/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ "setuptools", "pyparsing", "certifi", - "cibuildwheel", + "cibuildwheel==2.23.3", "pkginfo", "build", "packaging", diff --git a/eng/tools/azure-sdk-tools/tests/test_pypi_client.py b/eng/tools/azure-sdk-tools/tests/test_pypi_client.py index cb2de3dfa154..186837dd0349 100644 --- a/eng/tools/azure-sdk-tools/tests/test_pypi_client.py +++ b/eng/tools/azure-sdk-tools/tests/test_pypi_client.py @@ -1,41 +1,151 @@ -from pypi_tools.pypi import PyPIClient +from pypi_tools.pypi import PyPIClient, retrieve_versions_from_pypi from unittest.mock import patch import os import pytest -import pdb +from packaging.version import Version -class TestPyPiClient: - @pytest.mark.skipif( - os.environ.get("TF_BUILD", "None") == True, - reason=f"This test isn't worth recording and could be flaky. Skipping in CI.", - ) - def test_package_retrieve(self): - client = PyPIClient() - result = client.project("azure-core") +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- - # we won't _exhaustively_ check this, but we can sanity check a few proxy values to ensure we haven't broken anything - assert result["info"]["name"] == "azure-core" - assert len(result["releases"].keys()) > 47 - assert "1.25.1" in result["releases"] - assert "1.10.0" in result["releases"] +SKIP_IN_CI = pytest.mark.skipif( + os.environ.get("TF_BUILD", "None") == True, + reason="Live network test — skipped in CI.", +) + +PYPI_HOST = "https://pypi.org" +AZDO_FEED_URL = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple/" + +WELL_KNOWN_PACKAGE = "azure-core" +WELL_KNOWN_VERSION = "1.8.0" # old enough to always exist everywhere +MINIMUM_EXPECTED_VERSIONS = 47 # azure-core has *far* more than this + + +def _make_client(index_url: str) -> PyPIClient: + """Create a PyPIClient whose backend is driven by *index_url*. + + Temporarily sets PIP_INDEX_URL so the constructor picks the right backend. + """ + old = os.environ.get("PIP_INDEX_URL") + try: + if index_url: + os.environ["PIP_INDEX_URL"] = index_url + elif "PIP_INDEX_URL" in os.environ: + del os.environ["PIP_INDEX_URL"] + return PyPIClient() + finally: + if old is not None: + os.environ["PIP_INDEX_URL"] = old + elif "PIP_INDEX_URL" in os.environ: + del os.environ["PIP_INDEX_URL"] + + +# --------------------------------------------------------------------------- +# Tests parametrized across backends +# --------------------------------------------------------------------------- + + +@pytest.fixture(params=[PYPI_HOST, AZDO_FEED_URL], ids=["pypi", "azdo"]) +def client(request): + return _make_client(request.param) + + +class TestGetOrderedVersions: + """Covers the dominant call pattern: ~10 call-sites do get_ordered_versions().""" + + @SKIP_IN_CI + def test_returns_sorted_version_objects(self, client): + versions = client.get_ordered_versions(WELL_KNOWN_PACKAGE) + assert len(versions) >= MINIMUM_EXPECTED_VERSIONS + assert all(isinstance(v, Version) for v in versions) + assert versions == sorted(versions) + + @SKIP_IN_CI + def test_known_version_present(self, client): + versions = client.get_ordered_versions(WELL_KNOWN_PACKAGE) + version_strs = [str(v) for v in versions] + assert WELL_KNOWN_VERSION in version_strs + + +class TestGetRelevantVersions: + """Covers detect_breaking_changes.py usage of get_relevant_versions().""" + + @SKIP_IN_CI + def test_returns_latest_and_latest_stable(self, client): + latest, latest_stable = client.get_relevant_versions(WELL_KNOWN_PACKAGE) + assert isinstance(latest, Version) + assert isinstance(latest_stable, Version) + assert not latest_stable.is_prerelease + assert latest_stable <= latest - @pytest.mark.skipif( - os.environ.get("TF_BUILD", "None") == True, - reason=f"This test isn't worth recording and could be flaky. Skipping in CI.", - ) - def test_package_version_retrieve(self): + +class TestRetrieveVersions: + """Covers the convenience wrapper used by verify_sdist.py / verify_whl.py.""" + + @SKIP_IN_CI + @pytest.mark.parametrize("index_url", [PYPI_HOST, AZDO_FEED_URL], ids=["pypi", "azdo"]) + def test_retrieve_versions_returns_strings(self, index_url): + old = os.environ.get("PIP_INDEX_URL") + try: + if index_url: + os.environ["PIP_INDEX_URL"] = index_url + versions = retrieve_versions_from_pypi(WELL_KNOWN_PACKAGE) + finally: + if old is not None: + os.environ["PIP_INDEX_URL"] = old + elif "PIP_INDEX_URL" in os.environ: + del os.environ["PIP_INDEX_URL"] + + assert len(versions) >= MINIMUM_EXPECTED_VERSIONS + assert all(isinstance(v, str) for v in versions) + assert WELL_KNOWN_VERSION in versions + + +# --------------------------------------------------------------------------- +# project_release — works on both backends (AzDO falls back to pypi.org) +# --------------------------------------------------------------------------- + + +class TestProjectRelease: + """Covers functions.py:888 usage of project_release() for requires_dist.""" + + @SKIP_IN_CI + def test_project_release_returns_version_info(self, client): + result = client.project_release(WELL_KNOWN_PACKAGE, WELL_KNOWN_VERSION) + + assert result["info"]["name"] == WELL_KNOWN_PACKAGE + assert result["info"]["release_url"] == f"https://pypi.org/project/{WELL_KNOWN_PACKAGE}/{WELL_KNOWN_VERSION}/" + # requires_dist is what the mindep resolver reads + assert "requires_dist" in result["info"] + + +# --------------------------------------------------------------------------- +# PyPI-only tests (project / filter_packages_for_compatibility) +# --------------------------------------------------------------------------- + + +class TestPyPIOnlyMethods: + """Methods that only work against pypi.org JSON API. + + Callers: discover_unpublished_packages.py, output_old_packages.py. + """ + + @SKIP_IN_CI + def test_project_returns_info_and_releases(self): client = PyPIClient() - result = client.project_release("azure-core", "1.8.0") + result = client.project(WELL_KNOWN_PACKAGE) - assert result["info"]["name"] == "azure-core" - assert result["info"]["release_url"] == "https://pypi.org/project/azure-core/1.8.0/" + assert result["info"]["name"] == WELL_KNOWN_PACKAGE + assert len(result["releases"]) > MINIMUM_EXPECTED_VERSIONS + assert "1.25.1" in result["releases"] + assert WELL_KNOWN_VERSION in result["releases"] + @SKIP_IN_CI @patch("pypi_tools.pypi.sys") - def test_package_filter_for_compatibility(self, mock_sys): + def test_filter_packages_for_compatibility(self, mock_sys): mock_sys.version_info = (2, 7, 0) client = PyPIClient() - result = client.get_ordered_versions("azure-core", True) - unfiltered_results = client.get_ordered_versions("azure-core", False) - - assert len(result) < len(unfiltered_results) + filtered = client.get_ordered_versions(WELL_KNOWN_PACKAGE, True) + unfiltered = client.get_ordered_versions(WELL_KNOWN_PACKAGE, False) + assert len(filtered) < len(unfiltered) diff --git a/sdk/core/azure-core-experimental/tests/conftest.py b/sdk/core/azure-core-experimental/tests/conftest.py index 55134f14be5c..3d9f1674b350 100644 --- a/sdk/core/azure-core-experimental/tests/conftest.py +++ b/sdk/core/azure-core-experimental/tests/conftest.py @@ -36,7 +36,7 @@ def is_port_available(port_num): - req = urllib.request.Request("http://localhost:{}/health".format(port_num)) + req = urllib.request.Request("http://127.0.0.1:{}/health".format(port_num)) try: return urllib.request.urlopen(req).code != 200 except Exception as e: diff --git a/sdk/core/azure-core/dev_requirements.txt b/sdk/core/azure-core/dev_requirements.txt index 3ca5b687d635..c6c740fd4b75 100644 --- a/sdk/core/azure-core/dev_requirements.txt +++ b/sdk/core/azure-core/dev_requirements.txt @@ -12,4 +12,4 @@ azure-data-tables opentelemetry-sdk~=1.26 opentelemetry-instrumentation-requests>=0.50b0 ../../identity/azure-identity -packaging # for version parsing in test_basic_transport_async.py \ No newline at end of file +packaging # for version parsing in test_basic_transport_async.py diff --git a/sdk/core/azure-core/tests/async_tests/conftest.py b/sdk/core/azure-core/tests/async_tests/conftest.py index 59cdfe4a3674..5d6a720e0bde 100644 --- a/sdk/core/azure-core/tests/async_tests/conftest.py +++ b/sdk/core/azure-core/tests/async_tests/conftest.py @@ -35,7 +35,7 @@ def is_port_available(port_num): - req = urllib.request.Request("http://localhost:{}/health".format(port_num)) + req = urllib.request.Request("http://127.0.0.1:{}/health".format(port_num)) try: return urllib.request.urlopen(req).code != 200 except Exception as e: diff --git a/sdk/core/azure-core/tests/conftest.py b/sdk/core/azure-core/tests/conftest.py index 0d9ebb6c8c24..0570f7ea9c8f 100644 --- a/sdk/core/azure-core/tests/conftest.py +++ b/sdk/core/azure-core/tests/conftest.py @@ -45,7 +45,7 @@ def is_port_available(port_num): - req = urllib.request.Request("http://localhost:{}/health".format(port_num)) + req = urllib.request.Request("http://127.0.0.1:{}/health".format(port_num)) try: return urllib.request.urlopen(req).code != 200 except Exception as e: diff --git a/sdk/core/corehttp/tests/conftest.py b/sdk/core/corehttp/tests/conftest.py index 47a5756f9e76..a8f6f2fbb295 100644 --- a/sdk/core/corehttp/tests/conftest.py +++ b/sdk/core/corehttp/tests/conftest.py @@ -23,7 +23,7 @@ def is_port_available(port_num): - req = urllib.request.Request("http://localhost:{}/health".format(port_num)) + req = urllib.request.Request("http://127.0.0.1:{}/health".format(port_num)) try: return urllib.request.urlopen(req).code != 200 except Exception as e: