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

Support outputs in hint_noarch_python_use_python_min, also fix if/then/else flattening for non-list clauses #2218

Merged
merged 8 commits into from
Jan 15, 2025
21 changes: 2 additions & 19 deletions conda_smithy/lint_recipe.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
RATTLER_BUILD_TOOL,
find_local_config_file,
flatten_v1_if_else,
get_all_test_requirements,
get_section,
load_linter_toml_metdata,
)
Expand Down Expand Up @@ -525,25 +526,7 @@ def run_conda_forge_specific(

build_section = get_section(meta, "build", lints, recipe_version)
noarch_value = build_section.get("noarch")

if recipe_version == 1:
test_section = get_section(meta, "tests", lints, recipe_version)
test_reqs = []
for test_element in test_section:
test_reqs += (test_element.get("requirements") or {}).get(
"run"
) or []

if (
"python" in test_element
and test_element["python"].get("python_version") is not None
):
test_reqs.append(
f"python {test_element['python']['python_version']}"
)
else:
test_section = get_section(meta, "test", lints, recipe_version)
test_reqs = test_section.get("requires") or []
test_reqs = get_all_test_requirements(meta, lints, recipe_version)

# Fetch list of recipe maintainers
maintainers = extra_section.get("recipe-maintainers", [])
Expand Down
94 changes: 73 additions & 21 deletions conda_smithy/linter/hints.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import shutil
import subprocess
import sys
from collections.abc import Mapping
from glob import glob
from typing import Any

Expand All @@ -12,6 +13,7 @@
VALID_PYTHON_BUILD_BACKENDS,
find_local_config_file,
flatten_v1_if_else,
get_all_test_requirements,
is_selector_line,
)
from conda_smithy.utils import get_yaml
Expand Down Expand Up @@ -236,22 +238,22 @@ def hint_pip_no_build_backend(host_or_build_section, package_name, hints):
)


def hint_noarch_python_use_python_min(
def _hint_noarch_python_use_python_min_inner(
host_reqs,
run_reqs,
test_reqs,
outputs_section,
noarch_value,
recipe_version,
hints,
output_name,
):
if noarch_value == "python" and not outputs_section:
hint = []

if noarch_value == "python":
if recipe_version == 1:
host_reqs = flatten_v1_if_else(host_reqs)
run_reqs = flatten_v1_if_else(run_reqs)
test_reqs = flatten_v1_if_else(test_reqs)

hint = ""
for section_name, syntax, report_syntax, reqs in [
(
"host",
Expand Down Expand Up @@ -291,24 +293,74 @@ def hint_noarch_python_use_python_min(
):
break
else:
hint += (
f"\n - For the `{section_name}` section of the recipe, you should usually use `{report_syntax}` "
section_desc = (
f"`{output_name}` output" if output_name else "recipe"
)
hint.append(
f"\n - For the `{section_name}` section of {section_desc}, you should usually use `{report_syntax}` "
f"for the `python` entry."
)
return hint

if hint:
hint = (
(
"`noarch: python` recipes should usually follow the syntax in "
"our [documentation](https://conda-forge.org/docs/maintainer/knowledge_base/#noarch-python) "
"for specifying the Python version."
)
+ hint
+ (
"\n - If the package requires a newer Python version than the currently supported minimum "
"version on `conda-forge`, you can override the `python_min` variable by adding a "
"Jinja2 `set` statement at the top of your recipe (or using an equivalent `context` "
"variable for v1 recipes)."

def hint_noarch_python_use_python_min(
host_reqs,
run_reqs,
test_reqs,
outputs_section,
noarch_value,
recipe_version,
hints,
):
hint = []

if outputs_section:
for output_num, output in enumerate(outputs_section):
requirements = output.get("requirements", {})
if isinstance(requirements, Mapping):
output_host_reqs = requirements.get("host")
output_run_reqs = requirements.get("run")
else:
output_host_reqs = None
output_run_reqs = requirements

hint.extend(
_hint_noarch_python_use_python_min_inner(
output_host_reqs or [],
output_run_reqs or [],
get_all_test_requirements(output, [], recipe_version),
output.get("build", {}).get("noarch"),
recipe_version,
output.get("package", {}).get(
"name", f"<output {output_num}"
),
)
)
hints.append(hint)
else:
hint.extend(
_hint_noarch_python_use_python_min_inner(
host_reqs,
run_reqs,
test_reqs,
noarch_value,
recipe_version,
None,
)
)

if hint:
hint = (
(
"`noarch: python` recipes should usually follow the syntax in "
"our [documentation](https://conda-forge.org/docs/maintainer/knowledge_base/#noarch-python) "
"for specifying the Python version."
)
+ "".join(hint)
+ (
"\n - If the package requires a newer Python version than the currently supported minimum "
"version on `conda-forge`, you can override the `python_min` variable by adding a "
"Jinja2 `set` statement at the top of your recipe (or using an equivalent `context` "
"variable for v1 recipes)."
)
)
hints.append(hint)
36 changes: 33 additions & 3 deletions conda_smithy/linter/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,14 +237,44 @@ def load_linter_toml_metdata_internal(time_salt):
return tomllib.loads(hints_toml_str)


def flatten_v1_if_else(requirements: list[str | dict]) -> list[str]:
def flatten_v1_if_else(requirements: list[str | dict] | str) -> list[str]:
flattened_requirements = []
for req in requirements:
if isinstance(req, dict):
flattened_requirements.extend(flatten_v1_if_else(req["then"]))
flattened_requirements.extend(
flatten_v1_if_else(req.get("else") or [])
flatten_v1_if_else(req["then"])
if isinstance(req["then"], list)
else [req["then"]]
)
flattened_requirements.extend(
flatten_v1_if_else(req.get("else", []))
if isinstance(req.get("else", []), list)
else [req["else"]]
)
else:
flattened_requirements.append(req)
return flattened_requirements


def get_all_test_requirements(
meta: dict, lints: list[str], recipe_version: int
) -> list[str]:
if recipe_version == 1:
test_section = get_section(meta, "tests", lints, recipe_version)
test_reqs = []
for test_element in test_section:
test_reqs += (test_element.get("requirements") or {}).get(
"run"
) or []

if "python" in test_element:
if test_element["python"].get("python_version") is not None:
test_reqs.append(
f"python {test_element['python']['python_version']}"
)
else:
test_reqs.append("python")
else:
test_section = get_section(meta, "test", lints, recipe_version)
test_reqs = test_section.get("requires") or []
return test_reqs
23 changes: 23 additions & 0 deletions news/python-min-multi-output.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
**Added:**

* Added support for multi-output recipes in ``hint_noarch_python_use_python_min`` check. (#2218)

**Changed:**

* <news item>

**Deprecated:**

* <news item>

**Removed:**

* <news item>

**Fixed:**

* Fixed flattening ``if / then / else`` clauses in v1 recipes with string values of ``then`` and ``else`` keys. (#2218)

**Security:**

* <news item>
Loading
Loading