Repository: conda-forge/conda-smithy

Issue:
False positives for noarch: python syntax checks
### Solution to issue cannot be found in the documentation.

- [x] I checked the documentation.

### Issue

The `noarch: python` syntax check introduced in PR #2115 seems to be triggering in error on <https://github.com/conda-forge/esa-climate-toolbox-feedstock/pull/12> (`meta.yaml` contains exactly the syntax recommended by the hints). I reproduced this behaviour locally with `conda smithy lint --conda-forge recipe/`). As far as I can tell from some basic debugging, the linter is checking the syntax *after* the template has been rendered, so it's impossible to pass the check.

### Installed packages

```text
# packages in environment at /home/pont/mambaforge/envs/smithy:
#
# Name                    Version                   Build  Channel
_libgcc_mutex             0.1                 conda_forge    conda-forge
_openmp_mutex             4.5                       2_gnu    conda-forge
anyio                     4.6.2.post1        pyhd8ed1ab_0    conda-forge
archspec                  0.2.3              pyhd8ed1ab_0    conda-forge
attrs                     24.2.0             pyh71513ae_0    conda-forge
backports                 1.0                pyhd8ed1ab_4    conda-forge
backports.strenum         1.3.1              pyhd8ed1ab_0    conda-forge
backports.tarfile         1.2.0              pyhd8ed1ab_0    conda-forge
beautifulsoup4            4.12.3             pyha770c72_0    conda-forge
blinker                   1.8.2              pyhd8ed1ab_0    conda-forge
boltons                   24.0.0             pyhd8ed1ab_0    conda-forge
boolean.py                4.0                pyhd8ed1ab_0    conda-forge
brotli-python             1.1.0           py310hf71b8c6_2    conda-forge
bzip2                     1.0.8                h4bc722e_7    conda-forge
c-ares                    1.34.2               heb4867d_0    conda-forge
ca-certificates           2024.8.30            hbcca054_0    conda-forge
certifi                   2024.8.30          pyhd8ed1ab_0    conda-forge
cffi                      1.17.1          py310h8deb56e_0    conda-forge
chardet                   5.2.0           py310hff52083_2    conda-forge
charset-normalizer        3.4.0              pyhd8ed1ab_0    conda-forge
cirun                     0.30               pyhd8ed1ab_0    conda-forge
click                     8.1.7           unix_pyh707e725_0    conda-forge
colorama                  0.4.6              pyhd8ed1ab_0    conda-forge
conda                     24.9.2          py310hff52083_0    conda-forge
conda-build               24.9.0          py310hff52083_0    conda-forge
conda-forge-pinning       2024.11.06.14.03.29      hd8ed1ab_0    conda-forge
conda-index               0.5.0              pyhd8ed1ab_0    conda-forge
conda-libmamba-solver     24.9.0             pyhd8ed1ab_0    conda-forge
conda-package-handling    2.4.0              pyh7900ff3_0    conda-forge
conda-package-streaming   0.11.0             pyhd8ed1ab_0    conda-forge
conda-smithy              3.44.0          unix_pyhd81877a_0    conda-forge
cryptography              43.0.3          py310h6c63255_0    conda-forge
dbus                      1.13.6               h5008d03_3    conda-forge
deprecated                1.2.14             pyh1a96a4e_0    conda-forge
distlib                   0.3.9              pyhd8ed1ab_0    conda-forge
distro                    1.9.0              pyhd8ed1ab_0    conda-forge
editables                 0.5                pyhd8ed1ab_0    conda-forge
exceptiongroup            1.2.2              pyhd8ed1ab_0    conda-forge
expat                     2.6.4                h5888daf_0    conda-forge
filelock                  3.16.1             pyhd8ed1ab_0    conda-forge
fmt                       11.0.2               h434a139_0    conda-forge
frozendict                2.4.6           py310ha75aee5_0    conda-forge
git                       2.47.0          pl5321h59d505e_0    conda-forge
gitdb                     4.0.11             pyhd8ed1ab_0    conda-forge
gitpython                 3.1.43             pyhd8ed1ab_0    conda-forge
gmp                       6.3.0                hac33072_2    conda-forge
h11                       0.14.0             pyhd8ed1ab_0    conda-forge
h2                        4.1.0              pyhd8ed1ab_0    conda-forge
hatch                     1.13.0             pyhd8ed1ab_0    conda-forge
hatchling                 1.25.0             pyhd8ed1ab_0    conda-forge
hpack                     4.0.0              pyh9f0ad1d_0    conda-forge
httpcore                  1.0.6              pyhd8ed1ab_0    conda-forge
httpx                     0.27.2             pyhd8ed1ab_0    conda-forge
hyperframe                6.0.1              pyhd8ed1ab_0    conda-forge
hyperlink                 21.0.0             pyhd3deb0d_0    conda-forge
idna                      3.10               pyhd8ed1ab_0    conda-forge
importlib-metadata        8.5.0              pyha770c72_0    conda-forge
importlib_resources       6.4.5              pyhd8ed1ab_0    conda-forge
isodate                   0.7.2              pyhd8ed1ab_0    conda-forge
jaraco.classes            3.4.0              pyhd8ed1ab_1    conda-forge
jaraco.context            5.3.0              pyhd8ed1ab_1    conda-forge
jaraco.functools          4.0.0              pyhd8ed1ab_0    conda-forge
jeepney                   0.8.0              pyhd8ed1ab_0    conda-forge
jinja2                    3.1.4              pyhd8ed1ab_0    conda-forge
jsonpatch                 1.33               pyhd8ed1ab_0    conda-forge
jsonpointer               3.0.0           py310hff52083_1    conda-forge
jsonschema                4.23.0             pyhd8ed1ab_0    conda-forge
jsonschema-specifications 2024.10.1          pyhd8ed1ab_0    conda-forge
keyring                   25.5.0             pyha804496_0    conda-forge
keyutils                  1.6.1                h166bdaf_0    conda-forge
krb5                      1.21.3               h659f571_0    conda-forge
ld_impl_linux-64          2.43                 h712a8e2_2    conda-forge
libarchive                3.7.4                hfca40fe_0    conda-forge
libcurl                   8.11.0               hbbe4b11_0    conda-forge
libedit                   3.1.20191231         he28a2e2_2    conda-forge
libev                     4.33                 hd590300_2    conda-forge
libexpat                  2.6.4                h5888daf_0    conda-forge
libffi                    3.4.2                h7f98852_5    conda-forge
libgcc                    14.2.0               h77fa898_1    conda-forge
libgcc-ng                 14.2.0               h69a702a_1    conda-forge
libglib                   2.82.2               h2ff4ddf_0    conda-forge
libgomp                   14.2.0               h77fa898_1    conda-forge
libiconv                  1.17                 hd590300_2    conda-forge
liblief                   0.14.1               h5888daf_2    conda-forge
libmamba                  1.5.10               hf72d635_1    conda-forge
libmambapy                1.5.10          py310h6639945_1    conda-forge
libnghttp2                1.64.0               h161d5f1_0    conda-forge
libnsl                    2.0.1                hd590300_0    conda-forge
libsodium                 1.0.20               h4ab18f5_0    conda-forge
libsolv                   0.7.30               h3509ff9_0    conda-forge
libsqlite                 3.47.0               hadc24fc_1    conda-forge
libssh2                   1.11.0               h0841786_0    conda-forge
libstdcxx                 14.2.0               hc0a3c3a_1    conda-forge
libstdcxx-ng              14.2.0               h4852527_1    conda-forge
libuuid                   2.38.1               h0b41bf4_0    conda-forge
libxcrypt                 4.4.36               hd590300_1    conda-forge
libxml2                   2.13.4               h064dc61_2    conda-forge
libzlib                   1.3.1                hb9d3cd8_2    conda-forge
license-expression        30.3.1             pyhd8ed1ab_0    conda-forge
lz4-c                     1.9.4                hcb278e6_0    conda-forge
lzo                       2.10              hd590300_1001    conda-forge
markdown-it-py            3.0.0              pyhd8ed1ab_0    conda-forge
markupsafe                3.0.2           py310h89163eb_0    conda-forge
mdurl                     0.1.2              pyhd8ed1ab_0    conda-forge
menuinst                  2.1.2           py310hff52083_1    conda-forge
more-itertools            10.5.0             pyhd8ed1ab_0    conda-forge
msrest                    0.6.21             pyh44b312d_0    conda-forge
ncurses                   6.5                  he02047a_1    conda-forge
oauthlib                  3.2.2              pyhd8ed1ab_0    conda-forge
openssl                   3.3.2                hb9d3cd8_0    conda-forge
packaging                 24.1               pyhd8ed1ab_0    conda-forge
patch                     2.7.6             h7f98852_1002    conda-forge
patchelf                  0.17.2               h58526e2_0    conda-forge
pathspec                  0.12.1             pyhd8ed1ab_0    conda-forge
pcre2                     10.44                hba22ea6_2    conda-forge
perl                      5.32.1          7_hd590300_perl5    conda-forge
pexpect                   4.9.0              pyhd8ed1ab_0    conda-forge
pip                       24.3.1             pyh8b19718_0    conda-forge
pkginfo                   1.11.2             pyhd8ed1ab_0    conda-forge
pkgutil-resolve-name      1.3.10             pyhd8ed1ab_1    conda-forge
platformdirs              4.3.6              pyhd8ed1ab_0    conda-forge
pluggy                    1.5.0              pyhd8ed1ab_0    conda-forge
psutil                    6.1.0           py310ha75aee5_0    conda-forge
ptyprocess                0.7.0              pyhd3deb0d_0    conda-forge
py-lief                   0.14.1          py310hf71b8c6_2    conda-forge
pybind11-abi              4                    hd8ed1ab_3    conda-forge
pycosat                   0.6.6           py310h2372a71_0    conda-forge
pycparser                 2.22               pyhd8ed1ab_0    conda-forge
pycryptodome              3.21.0          py310hb390ff4_0    conda-forge
pygithub                  2.5.0              pyhd8ed1ab_0    conda-forge
pygments                  2.18.0             pyhd8ed1ab_0    conda-forge
pyjwt                     2.9.0              pyhd8ed1ab_1    conda-forge
pynacl                    1.5.0           py310ha75aee5_4    conda-forge
pysocks                   1.7.1              pyha2e5f31_6    conda-forge
python                    3.10.15         h4a871b0_2_cpython    conda-forge
python-dateutil           2.9.0              pyhd8ed1ab_0    conda-forge
python-libarchive-c       5.1             py310hff52083_1    conda-forge
python_abi                3.10                    5_cp310    conda-forge
pytz                      2024.2             pyhd8ed1ab_0    conda-forge
pyyaml                    6.0.2           py310ha75aee5_1    conda-forge
rattler-build             0.29.0               h51b9b6e_0    conda-forge
rattler-build-conda-compat 1.2.2              pyhd8ed1ab_0    conda-forge
readline                  8.2                  h8228510_1    conda-forge
referencing               0.35.1             pyhd8ed1ab_0    conda-forge
reproc                    14.2.4.post0         hd590300_1    conda-forge
reproc-cpp                14.2.4.post0         h59595ed_1    conda-forge
requests                  2.32.3             pyhd8ed1ab_0    conda-forge
requests-oauthlib         2.0.0              pyhd8ed1ab_0    conda-forge
rich                      13.9.4             pyhd8ed1ab_0    conda-forge
ripgrep                   14.1.1               h8fae777_0    conda-forge
rpds-py                   0.21.0          py310h505e2c1_0    conda-forge
ruamel.yaml               0.18.6          py310ha75aee5_1    conda-forge
ruamel.yaml.clib          0.2.8           py310ha75aee5_1    conda-forge
scrypt                    0.8.27          py310h6ec971a_0    conda-forge
secretstorage             3.3.3           py310hff52083_3    conda-forge
setuptools                75.3.0             pyhd8ed1ab_0    conda-forge
shellingham               1.5.4              pyhd8ed1ab_0    conda-forge
six                       1.16.0             pyh6c4a22f_0    conda-forge
smmap                     5.0.0              pyhd8ed1ab_0    conda-forge
sniffio                   1.3.1              pyhd8ed1ab_0    conda-forge
soupsieve                 2.5                pyhd8ed1ab_1    conda-forge
tk                        8.6.13          noxft_h4845f30_101    conda-forge
tomli                     2.0.2              pyhd8ed1ab_0    conda-forge
tomli-w                   1.1.0              pyhd8ed1ab_0    conda-forge
tomlkit                   0.13.2             pyha770c72_0    conda-forge
toolz                     1.0.0              pyhd8ed1ab_0    conda-forge
tqdm                      4.67.0             pyhd8ed1ab_0    conda-forge
trove-classifiers         2024.10.21.16      pyhd8ed1ab_0    conda-forge
truststore                0.10.0             pyhd8ed1ab_0    conda-forge
typer                     0.12.5             pyhd8ed1ab_0    conda-forge
typer-slim                0.12.5             pyhd8ed1ab_0    conda-forge
typer-slim-standard       0.12.5               hd8ed1ab_0    conda-forge
typing-extensions         4.12.2               hd8ed1ab_0    conda-forge
typing_extensions         4.12.2             pyha770c72_0    conda-forge
tzdata                    2024b                hc8b5060_0    conda-forge
urllib3                   2.2.3              pyhd8ed1ab_0    conda-forge
userpath                  1.7.0              pyhd8ed1ab_0    conda-forge
uv                        0.4.30               h0f3a69f_0    conda-forge
virtualenv                20.27.1            pyhd8ed1ab_0    conda-forge
vsts-python-api           0.1.25             pyhd8ed1ab_1    conda-forge
wheel                     0.44.0             pyhd8ed1ab_0    conda-forge
wrapt                     1.16.0          py310ha75aee5_1    conda-forge
xz                        5.2.6                h166bdaf_0    conda-forge
yaml                      0.2.5                h7f98852_2    conda-forge
yaml-cpp                  0.8.0                h59595ed_0    conda-forge
zipp                      3.20.2             pyhd8ed1ab_0    conda-forge
zstandard                 0.23.0          py310ha39cb0e_1    conda-forge
zstd                      1.5.6                ha6fb4c9_0    conda-forge
```

### Environment info

```text
     active environment : smithy
    active env location : /home/pont/mambaforge/envs/smithy
            shell level : 1
       user config file : /home/pont/.condarc
 populated config files : /home/pont/mambaforge/.condarc
                          /home/pont/.condarc
          conda version : 24.7.1
    conda-build version : not installed
         python version : 3.10.8.final.0
                 solver : libmamba (default)
       virtual packages : __archspec=1=zen
                          __conda=24.7.1=0
                          __glibc=2.31=0
                          __linux=5.15.0=0
                          __unix=0=0
       base environment : /home/pont/mambaforge  (writable)
      conda av data dir : /home/pont/mambaforge/etc/conda
  conda av metadata url : None
           channel URLs : https://conda.anaconda.org/conda-forge/linux-64
                          https://conda.anaconda.org/conda-forge/noarch
          package cache : /home/pont/mambaforge/pkgs
                          /home/pont/.conda/pkgs
       envs directories : /home/pont/mambaforge/envs
                          /home/pont/.conda/envs
               platform : linux-64
             user-agent : conda/24.7.1 requests/2.32.3 CPython/3.10.8 Linux/5.15.0-124-generic ubuntu/20.04.6 glibc/2.31 solver/libmamba conda-libmamba-solver/24.7.0 libmambapy/1.5.10
                UID:GID : 1000:1000
             netrc file : None
           offline mode : False
```

Discussion:

Patch:
diff --git a/conda_smithy/linter/hints.py b/conda_smithy/linter/hints.py
index a2c21790..d5665f8f 100644
--- a/conda_smithy/linter/hints.py
+++ b/conda_smithy/linter/hints.py
@@ -250,6 +250,8 @@ def hint_noarch_python_use_python_min(
             ("run", "python >={{ python_min }}", run_reqs),
             ("test.requires", "python {{ python_min }}", test_reqs),
         ]:
+            test_syntax = syntax.replace("{{ python_min }}", "9999")
+
             if recipe_version == 1:
                 syntax = syntax.replace(
                     "{{ python_min }}", "${{ python_min }}"
@@ -259,7 +261,7 @@ def hint_noarch_python_use_python_min(
                 if (
                     req.strip().split()[0] == "python"
                     and req != "python"
-                    and syntax in req
+                    and test_syntax in req
                 ):
                     break
             else:
diff --git a/conda_smithy/utils.py b/conda_smithy/utils.py
index 4d94461c..c4f58369 100644
--- a/conda_smithy/utils.py
+++ b/conda_smithy/utils.py
@@ -140,6 +140,7 @@ def render_meta_yaml(text):
             target_platform="linux-64",
             build_platform="linux-64",
             mpi="mpi",
+            python_min="9999",  # use as a sentinel value for linting
         )
     )
     mockos = MockOS()
diff --git a/news/2123-more-noarch-python-fixes.rst b/news/2123-more-noarch-python-fixes.rst
new file mode 100644
index 00000000..e4a9daa7
--- /dev/null
+++ b/news/2123-more-noarch-python-fixes.rst
@@ -0,0 +1,23 @@
+**Added:**
+
+* <news item>
+
+**Changed:**
+
+* <news item>
+
+**Deprecated:**
+
+* <news item>
+
+**Removed:**
+
+* <news item>
+
+**Fixed:**
+
+* Fixed false positives in the ``noarch: python`` check. (#2123)
+
+**Security:**
+
+* <news item>


Test patch:
diff --git a/tests/test_lint_recipe.py b/tests/test_lint_recipe.py
index 178c7ab6..42c009f3 100644
--- a/tests/test_lint_recipe.py
+++ b/tests/test_lint_recipe.py
@@ -13,7 +13,7 @@ import pytest
 
 import conda_smithy.lint_recipe as linter
 from conda_smithy.linter.utils import VALID_PYTHON_BUILD_BACKENDS
-from conda_smithy.utils import get_yaml
+from conda_smithy.utils import get_yaml, render_meta_yaml
 
 _thisdir = os.path.abspath(os.path.dirname(__file__))
 
@@ -3163,7 +3163,7 @@ def test_hint_noarch_python_use_python_min(
     meta_str,
     expected_hints,
 ):
-    meta = get_yaml().load(meta_str)
+    meta = get_yaml().load(render_meta_yaml(meta_str))
     lints = []
     hints = []
     linter.run_conda_forge_specific(
@@ -3250,7 +3250,7 @@ def test_hint_noarch_python_use_python_min_v1(
     meta_str,
     expected_hints,
 ):
-    meta = get_yaml().load(meta_str)
+    meta = get_yaml().load(render_meta_yaml(meta_str.replace("${{", "{{")))
     lints = []
     hints = []
     linter.run_conda_forge_specific(