Skip to content

Conversation

@AnneBeyer
Copy link
Contributor

@AnneBeyer AnneBeyer commented Dec 1, 2025

Reference Issues/PRs

Related to #32790 (comment)

What does this implement/fix? Explain your changes.

This adds a test using a minimal pyproject sample, to test that the tests actually work with upper bounds.
I had to restructure the code a bit in order to use a test string instead of the toml file.
Let me know if this is what you had in mind @lesteve.

Any other comments?

I noticed that there are also no tests testing the mismatch conditions of package versions. Adding this would be fairly straight forward now, but I'm not sure if these files are actually touched manually, and if this would make sense?

@github-actions
Copy link

github-actions bot commented Dec 1, 2025

✔️ Linting Passed

All linting checks passed. Your pull request is in excellent shape! ☀️

Generated for commit: 0d18fb1. Link to the linter CI: here

):
# tomllib is available in Python 3.11
tomllib = pytest.importorskip("tomllib")
def extract_packages_and_pyproject_tags(dependencies):
Copy link
Member

@lesteve lesteve Dec 3, 2025

Choose a reason for hiding this comment

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

Thanks for the PR! This seems to go in the direction I had in mind, I'll try to have a closer look at one point.

General comment: the more you can reduce the diff, the more this will make the reviewer's life (my life in this particular case) easier 😉. For example, it seems like you move global variables min_dependencies_tag_to_packages_without_version and pyproject_section_to_min_dependencies_tag to a function but this function is called once.

Maybe you have a good reason to do it and I would be curious to hear about it, but as a reviewer, I am expecting an "easy" PR to review and I have to take 5-10 minutes to understand just this part and that doesn't help keeping this PR in the "easy to review" category 😅.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved the global variables into a function in order to create them either from the min_dependencies or the test_dependencies, meaning the function is only called once, but the parameter passed from test_min_dependencies_pyproject_toml and test_check_pyproject_section_with_upper_bounds differs.
One could integrate the code into check_pyproject_sections, but since it is sort of a pre-processing step, I decided to keep it as an own function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried to move the function to the same place as the global variables to make it easier to compare, but github still aligned the wrong parts, so I'll try to mark the changes below instead.

@AnneBeyer
Copy link
Contributor Author

Thank you for the feedback!
I agree that the diff is really hard to read.

I had to fiddle around with the code a bit to make it easy to switch between real dependencies and a minimal test (because the code does not only check if the versions are the same, but also if the specified packages are the same (which makes sense), so I had to create matching minimal test samples for both min_dependencies and pyproject).

I tried to reduce the differences, and comparing this version to main in VSCode (using GitLens) actually doesn't look too bad, but here on GitHub it tries to align the wrong overlapping parts... 😞
I'll try to add some more explanations in the code, let me know if you can work with that.

@AnneBeyer AnneBeyer marked this pull request as draft December 3, 2025 21:11
tomllib = pytest.importorskip("tomllib")
def extract_packages_and_pyproject_tags(dependencies):
min_depencies_tag_to_packages_without_version = defaultdict(list)
for package, (min_version, tags) in dependencies.items():
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, it now iterates over the argument dependencies instead of the global imported dependent_packages

(I took the liberty of also renaming extras to tags, because it took my a while to figure out what "extras" are.)

def check_pyproject_sections(pyproject_toml, min_dependencies):
packages, pyproject_tags = extract_packages_and_pyproject_tags(min_dependencies)

for pyproject_section, min_dependencies_tag in pyproject_tags.items():
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I replaced the parametrization in the existing test with a for-loop here, to enable iterating over either the whole actual pyproject.toml or the given test subset.

for pyproject_section, min_dependencies_tag in pyproject_tags.items():
# NumPy is more complex because build-time (>=1.25) and run-time (>=1.19.5)
# requirement currently don't match
skip_version_check_for = ["numpy"] if min_dependencies_tag == "build" else []
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the tags are only available at this stage, the definition of the skip packages has to happen here now.


pyproject_section_keys = pyproject_section.split(".")
info = pyproject_toml
# iterate through nested keys to get packages and version
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added a comment here because it took me a while to understand the following loop and what info actually contains at the end.

Copy link
Contributor Author

@AnneBeyer AnneBeyer Dec 3, 2025

Choose a reason for hiding this comment

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

The remainder of this function is almost unchanged except for one extra indentation because of the loop above and one other change marked below.


@pytest.mark.parametrize(
"pyproject_section, min_dependencies_tag",
pyproject_section_to_min_dependencies_tag.items(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This parametrization is replaced by the for-loop in check_pyproject_sections(

def test_min_dependencies_pyproject_toml():
"""Check versions in pyproject.toml is consistent with _min_dependencies."""

root_directory = Path(sklearn.__file__).parent.parent
Copy link
Contributor Author

@AnneBeyer AnneBeyer Dec 3, 2025

Choose a reason for hiding this comment

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

Reading in the actual pyproject.toml file is now happening here, and the parsed version is passed to check_pyproject_sections (to enable passing a test version in the new test)


for package, version in pyproject_build_min_versions.items():
version = parse_version(version)
expected_min_version = parse_version(min_dependencies[package][0])
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here, the version is compared to the given min_dependencies, not the global imported dependent_packages (as in line 119 in the old version)

@AnneBeyer AnneBeyer marked this pull request as ready for review December 3, 2025 21:58
Copy link
Member

@ogrisel ogrisel left a comment

Choose a reason for hiding this comment

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

Please find a pass of review below.

pyproject_section_to_min_dependencies_tag = {
"build-system.requires": "build",
"project.dependencies": "install",
MIN_DEPENDENT_PACKAGES = {
Copy link
Member

Choose a reason for hiding this comment

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

I think we could use more explicit variable names to convey that those are not expected to match the currently used version numbers.

Suggested change
MIN_DEPENDENT_PACKAGES = {
EXAMPLE_MIN_DEPENDENT_PACKAGES = {

section = f"project.optional-dependencies.{tag}"
pyproject_section_to_min_dependencies_tag[section] = tag

MATCHING_PYPROJECT_SECTIONS_WITH_UPPER_BOUND = """
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
MATCHING_PYPROJECT_SECTIONS_WITH_UPPER_BOUND = """
EXAMPLE_EXPECTED_PYPROJECT_SECTIONS_WITH_UPPER_BOUND = """

install = ["joblib>=1.3.0,<2.0", "scipy==1.10.0"]
[build-system]
requires = ["scipy>=1.10.0,<1.19.0"]
"""
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add a similar example pyproject without the upper bounds to also test the check function against it?

min_dependencies_tag,
skip_version_check_for=skip_version_check_for,
)
check_pyproject_sections(pyproject_toml, MIN_DEPENDENT_PACKAGES)
Copy link
Member

Choose a reason for hiding this comment

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

Please also expand the test to also checks that a meaningful exception is raised when inconsistent arguments are passed to check_pyproject_sections.

@AnneBeyer AnneBeyer changed the title TST add test for pyproject minimum dependency checks with upper bound TST add tests for pyproject minimum dependency checks Dec 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants