diff --git a/upgrade/scripts/find_compatible_versions.py b/upgrade/scripts/find_compatible_versions.py
index 1ca5302..777d6f3 100644
--- a/upgrade/scripts/find_compatible_versions.py
+++ b/upgrade/scripts/find_compatible_versions.py
@@ -17,7 +17,11 @@
parse_requirements_txt,
to_requirements_obj,
)
-from upgrade.scripts.utils import get_venv_executable, is_package_already_installed
+from upgrade.scripts.utils import (
+ get_venv_executable,
+ is_development_cloudsmith,
+ is_package_already_installed,
+)
from upgrade.scripts.validations import is_cloudsmith_url_valid
@@ -48,12 +52,16 @@ def get_compatible_upgrade_versions(
tree = et.HTML(package_index_html)
anchor_tags_el = tree.xpath("//a")
parsed_packages_versions = [
- parse_wheel_filename(tag_el.text)[1] for tag_el in anchor_tags_el
+ parse_wheel_filename(tag_el.text)[1]
+ for tag_el in anchor_tags_el
+ if tag_el.text and tag_el.text.endswith(".whl")
]
logging.debug(f"Parsed packages versions: {parsed_packages_versions}")
compatible_versions = filter_versions(
- requirements_obj.specifier, parsed_packages_versions
+ requirements_obj.specifier,
+ parsed_packages_versions,
+ prereleases=is_development_cloudsmith(cloudsmith_url),
)
logging.debug(f"Found compatible versions: {compatible_versions}")
diff --git a/upgrade/scripts/requirements.py b/upgrade/scripts/requirements.py
index 8775d77..fa264e7 100644
--- a/upgrade/scripts/requirements.py
+++ b/upgrade/scripts/requirements.py
@@ -9,7 +9,7 @@
def filter_versions(
- specifier_set: Any, parsed_packages_versions: List[Any]
+ specifier_set: Any, parsed_packages_versions: List[Any], prereleases: bool = False
) -> List[str]:
"""Returns a list of versions that are compatible with the `SpecifierSet`.
@@ -21,8 +21,15 @@ def filter_versions(
or:
SpecifierSet("==2.5.14").filter(["2.5.14", "2.5.15", "2.6.0", "3.0.0"])
returns ["2.5.14"]
+
+ prereleases defaults to False, matching SpecifierSet.filter()'s own default - pass True
+ for a dev/pre-release channel whose versions are all pre-releases, or every version
+ would otherwise be filtered out and no upgrade would ever be found.
"""
- return [str(version) for version in specifier_set.filter(parsed_packages_versions)]
+ return [
+ str(version)
+ for version in specifier_set.filter(parsed_packages_versions, prereleases=prereleases)
+ ]
def parse_requirements_txt(
diff --git a/upgrade/tests/manage_venv/conftest.py b/upgrade/tests/manage_venv/conftest.py
index 397ce98..bd0e369 100644
--- a/upgrade/tests/manage_venv/conftest.py
+++ b/upgrade/tests/manage_venv/conftest.py
@@ -124,6 +124,35 @@ def mock_package_index_html():
yield
+@pytest.fixture()
+def mock_package_index_html_with_sdist_entry():
+ """Mirrors a real Cloudsmith index page that also lists a non-wheel sdist
+ (e.g. a stray oll-test-top-level-0.0.0.tar.gz from an earlier bad publish),
+ to guard against parse_wheel_filename crashing on it.
+ """
+ index_html_page = """
+
+
+
+ Mock package index html page for testing compatible dependencies script.
+
+
+ Links for oll-top-level-package
+ oll-test-top-level-0.0.0.tar.gz
+ oll_test_top_level-1.0.0-py3-none-any.whl
+ oll_test_top_level-2.0.0-py3-none-any.whl
+ oll_test_top_level-2.0.1-py3-none-any.whl
+ oll_test_top_level-2.1.0-py3-none-any.whl
+
+
+ """
+ with patch(
+ "upgrade.scripts.find_compatible_versions._get_package_index_html",
+ lambda *_,: index_html_page,
+ ):
+ yield
+
+
def install_upgrade_python_package(venv_executable, *rest):
run(
*([
diff --git a/upgrade/tests/manage_venv/test_get_compatible_upgrade_versions.py b/upgrade/tests/manage_venv/test_get_compatible_upgrade_versions.py
index 066d987..f13e62b 100644
--- a/upgrade/tests/manage_venv/test_get_compatible_upgrade_versions.py
+++ b/upgrade/tests/manage_venv/test_get_compatible_upgrade_versions.py
@@ -59,3 +59,22 @@ def test_get_compatible_version_where_current_is_2_0_0_and_venv_without_specifie
expected = "2.1.0"
assert actual == expected
+
+
+def test_get_compatible_version_where_index_page_lists_a_non_wheel_sdist_expect_it_is_ignored(
+ initial_v2_0_0_venv,
+ envs_home,
+ mock_package_index_html_with_sdist_entry,
+):
+ cut = get_compatible_version
+
+ requirements = "oll-test-top-level~=2.0.0"
+ venv_path = Path(envs_home, requirements)
+
+ actual = cut(
+ requirements_obj=to_requirements_obj(requirements),
+ venv_path=str(venv_path),
+ )
+ expected = "2.0.1"
+
+ assert actual == expected
diff --git a/upgrade/tests/upgrade_package/test_filter_versions.py b/upgrade/tests/upgrade_package/test_filter_versions.py
index f94201a..0436cee 100644
--- a/upgrade/tests/upgrade_package/test_filter_versions.py
+++ b/upgrade/tests/upgrade_package/test_filter_versions.py
@@ -25,3 +25,21 @@ def test_filter_versions_with_different_specifiers(
actual = cut(specifier_set, available_versions)
assert actual == expected
+
+
+def test_filter_versions_where_channel_is_dev_only_expect_none_found_by_default():
+ cut = filter_versions
+
+ actual = cut(SpecifierSet("~=2.21"), ["2.21.6.dev184", "2.21.6.dev200"])
+
+ assert actual == []
+
+
+def test_filter_versions_where_channel_is_dev_only_and_prereleases_true_expect_all_found():
+ cut = filter_versions
+
+ actual = cut(
+ SpecifierSet("~=2.21"), ["2.21.6.dev184", "2.21.6.dev200"], prereleases=True
+ )
+
+ assert actual == ["2.21.6.dev184", "2.21.6.dev200"]
diff --git a/upgrade/tests/upgrade_package/test_integ_find_compatible_versions_live.py b/upgrade/tests/upgrade_package/test_integ_find_compatible_versions_live.py
new file mode 100644
index 0000000..f01d583
--- /dev/null
+++ b/upgrade/tests/upgrade_package/test_integ_find_compatible_versions_live.py
@@ -0,0 +1,32 @@
+import os
+
+import pytest
+from packaging.version import Version
+
+from upgrade.scripts.find_compatible_versions import get_compatible_upgrade_versions
+from upgrade.scripts.requirements import to_requirements_obj
+
+# Set to a live openlawlibrary/development Cloudsmith index URL (with the entitlement
+# token embedded, e.g. https://dl.cloudsmith.io//openlawlibrary/development/python/index/)
+# to run this against the real index instead of a mocked one. Skipped otherwise, since it
+# needs real network access and a real credential this repo doesn't store.
+CLOUDSMITH_DEV_URL = os.environ.get("CLOUDSMITH_DEV_URL_TEST")
+
+pytestmark = pytest.mark.skipif(
+ not CLOUDSMITH_DEV_URL,
+ reason="CLOUDSMITH_DEV_URL_TEST is not set - export a live development Cloudsmith "
+ "index URL to exercise this against the real index",
+)
+
+
+def test_get_compatible_upgrade_versions_where_dev_channel_expect_prereleases_included():
+ cut = get_compatible_upgrade_versions
+
+ requirements_obj = to_requirements_obj("oll-cls~=2.21")
+ versions = cut(requirements_obj, CLOUDSMITH_DEV_URL)
+
+ assert versions, "expected at least one dev version from the live development index"
+ assert any(Version(v) > Version("2.21.6.dev184") for v in versions), (
+ "expected a version newer than the known-stale 2.21.6.dev184 to be present - "
+ "if this fails, prereleases are being filtered out again (the bug this test guards)"
+ )