Skip to content

Commit 8ed737b

Browse files
Merge pull request #1176 from RonnyPfannschmidt/fix-968-better-override-env-var-name
Fix 986 better override env var name
2 parents b9d03e4 + becaeb4 commit 8ed737b

File tree

6 files changed

+387
-17
lines changed

6 files changed

+387
-17
lines changed

docs/config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ Callables or other Python objects have to be passed in `setup.py` (via the `use_
157157
it is strongly recommended to use distribution-specific pretend versions
158158
(see below).
159159

160-
`SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME}`
160+
`SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}`
161161
: used as the primary source for the version number,
162162
in which case it will be an unparsed string.
163163
Specifying distribution-specific pretend versions will

docs/integrations.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ build:
4747
- export SETUPTOOLS_SCM_OVERRIDES_FOR_${READTHEDOCS_PROJECT//-/_}='{scm.git.pre_parse="fail_on_shallow"}'
4848
```
4949

50-
This configuration uses the `SETUPTOOLS_SCM_OVERRIDES_FOR_${NORMALIZED_DIST_NAME}` environment variable to override the `scm.git.pre_parse` setting specifically for your project when building on ReadTheDocs, forcing setuptools-scm to fail with a clear error if the repository is shallow.
50+
This configuration uses the `SETUPTOOLS_SCM_OVERRIDES_FOR_${DIST_NAME}` environment variable to override the `scm.git.pre_parse` setting specifically for your project when building on ReadTheDocs, forcing setuptools-scm to fail with a clear error if the repository is shallow.
5151

5252
## CI/CD and Package Publishing
5353

@@ -67,7 +67,7 @@ These local version components (`+g1a2b3c4d5`, `+dirty`) prevent uploading to Py
6767

6868
#### The Solution
6969

70-
Use the `SETUPTOOLS_SCM_OVERRIDES_FOR_${NORMALIZED_DIST_NAME}` environment variable to override the `local_scheme` to `no-local-version` when building for upload to PyPI.
70+
Use the `SETUPTOOLS_SCM_OVERRIDES_FOR_${DIST_NAME}` environment variable to override the `local_scheme` to `no-local-version` when building for upload to PyPI.
7171

7272
### GitHub Actions Example
7373

@@ -287,9 +287,9 @@ publish-release:
287287

288288
#### Environment Variable Format
289289

290-
The environment variable `SETUPTOOLS_SCM_OVERRIDES_FOR_${NORMALIZED_DIST_NAME}` must be set where:
290+
The environment variable `SETUPTOOLS_SCM_OVERRIDES_FOR_${DIST_NAME}` must be set where:
291291

292-
1. **`${NORMALIZED_DIST_NAME}`** is your package name normalized according to PEP 503:
292+
1. **`${DIST_NAME}`** is your package name normalized according to PEP 503:
293293
- Convert to uppercase
294294
- Replace hyphens and dots with underscores
295295
- Examples: `my-package` → `MY_PACKAGE`, `my.package` → `MY_PACKAGE`

docs/overrides.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ setuptools-scm provides a mechanism to override the version number build time.
77
the environment variable `SETUPTOOLS_SCM_PRETEND_VERSION` is used
88
as the override source for the version number unparsed string.
99

10-
to be specific about the package this applies for, one can use `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME}`
10+
to be specific about the package this applies for, one can use `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}`
1111
where the dist name normalization follows adapted PEP 503 semantics.
1212

1313
## pretend metadata
@@ -17,7 +17,7 @@ setuptools-scm provides a mechanism to override individual version metadata fiel
1717
The environment variable `SETUPTOOLS_SCM_PRETEND_METADATA` accepts a TOML inline table
1818
with field overrides for the ScmVersion object.
1919

20-
To be specific about the package this applies for, one can use `SETUPTOOLS_SCM_PRETEND_METADATA_FOR_${NORMALIZED_DIST_NAME}`
20+
To be specific about the package this applies for, one can use `SETUPTOOLS_SCM_PRETEND_METADATA_FOR_${DIST_NAME}`
2121
where the dist name normalization follows adapted PEP 503 semantics.
2222

2323
### Supported fields
@@ -82,7 +82,7 @@ export SETUPTOOLS_SCM_PRETEND_METADATA='{node="g1337beef", distance=4}'
8282

8383
## config overrides
8484

85-
setuptools-scm parses the environment variable `SETUPTOOLS_SCM_OVERRIDES_FOR_${NORMALIZED_DIST_NAME}`
85+
setuptools-scm parses the environment variable `SETUPTOOLS_SCM_OVERRIDES_FOR_${DIST_NAME}`
8686
as a toml inline map to override the configuration data from `pyproject.toml`.
8787

8888
## subprocess timeouts

docs/usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ Note that running this Dockerfile requires docker with BuildKit enabled
221221

222222
To avoid BuildKit and mounting of the .git folder altogether, one can also pass the desired
223223
version as a build argument.
224-
Note that `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${NORMALIZED_DIST_NAME}`
224+
Note that `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_${DIST_NAME}`
225225
is preferred over `SETUPTOOLS_SCM_PRETEND_VERSION`.
226226

227227

src/setuptools_scm/_overrides.py

Lines changed: 132 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
import dataclasses
44
import os
5-
import re
65

6+
from difflib import get_close_matches
77
from typing import Any
8+
from typing import Mapping
9+
10+
from packaging.utils import canonicalize_name
811

912
from . import _config
1013
from . import _log
@@ -19,18 +22,139 @@
1922
PRETEND_METADATA_KEY_NAMED = PRETEND_METADATA_KEY + "_FOR_{name}"
2023

2124

25+
def _search_env_vars_with_prefix(
26+
prefix: str, dist_name: str, env: Mapping[str, str]
27+
) -> list[tuple[str, str]]:
28+
"""Search environment variables with a given prefix for potential dist name matches.
29+
30+
Args:
31+
prefix: The environment variable prefix (e.g., "SETUPTOOLS_SCM_PRETEND_VERSION_FOR_")
32+
dist_name: The original dist name to match against
33+
env: Environment dictionary to search in
34+
35+
Returns:
36+
List of (env_var_name, env_var_value) tuples for potential matches
37+
"""
38+
# Get the canonical name for comparison
39+
canonical_dist_name = canonicalize_name(dist_name)
40+
41+
matches = []
42+
for env_var, value in env.items():
43+
if env_var.startswith(prefix):
44+
suffix = env_var[len(prefix) :]
45+
# Normalize the suffix and compare to canonical dist name
46+
try:
47+
normalized_suffix = canonicalize_name(suffix.lower().replace("_", "-"))
48+
if normalized_suffix == canonical_dist_name:
49+
matches.append((env_var, value))
50+
except Exception:
51+
# If normalization fails for any reason, skip this env var
52+
continue
53+
54+
return matches
55+
56+
57+
def _find_close_env_var_matches(
58+
prefix: str, expected_suffix: str, env: Mapping[str, str], threshold: float = 0.6
59+
) -> list[str]:
60+
"""Find environment variables with similar suffixes that might be typos.
61+
62+
Args:
63+
prefix: The environment variable prefix
64+
expected_suffix: The expected suffix (canonicalized dist name in env var format)
65+
env: Environment dictionary to search in
66+
threshold: Similarity threshold for matches (0.0 to 1.0)
67+
68+
Returns:
69+
List of environment variable names that are close matches
70+
"""
71+
candidates = []
72+
for env_var in env:
73+
if env_var.startswith(prefix):
74+
suffix = env_var[len(prefix) :]
75+
candidates.append(suffix)
76+
77+
# Use difflib to find close matches
78+
close_matches = get_close_matches(
79+
expected_suffix, candidates, n=3, cutoff=threshold
80+
)
81+
82+
return [f"{prefix}{match}" for match in close_matches if match != expected_suffix]
83+
84+
2285
def read_named_env(
23-
*, tool: str = "SETUPTOOLS_SCM", name: str, dist_name: str | None
86+
*,
87+
tool: str = "SETUPTOOLS_SCM",
88+
name: str,
89+
dist_name: str | None,
90+
env: Mapping[str, str] = os.environ,
2491
) -> str | None:
25-
""" """
92+
"""Read a named environment variable, with fallback search for dist-specific variants.
93+
94+
This function first tries the standard normalized environment variable name.
95+
If that's not found and a dist_name is provided, it searches for alternative
96+
normalizations and warns about potential issues.
97+
98+
Args:
99+
tool: The tool prefix (default: "SETUPTOOLS_SCM")
100+
name: The environment variable name component
101+
dist_name: The distribution name for dist-specific variables
102+
env: Environment dictionary to search in (defaults to os.environ)
103+
104+
Returns:
105+
The environment variable value if found, None otherwise
106+
"""
107+
108+
# First try the generic version
109+
generic_val = env.get(f"{tool}_{name}")
110+
26111
if dist_name is not None:
27-
# Normalize the dist name as per PEP 503.
28-
normalized_dist_name = re.sub(r"[-_.]+", "-", dist_name)
29-
env_var_dist_name = normalized_dist_name.replace("-", "_").upper()
30-
val = os.environ.get(f"{tool}_{name}_FOR_{env_var_dist_name}")
112+
# Normalize the dist name using packaging.utils.canonicalize_name
113+
canonical_dist_name = canonicalize_name(dist_name)
114+
env_var_dist_name = canonical_dist_name.replace("-", "_").upper()
115+
expected_env_var = f"{tool}_{name}_FOR_{env_var_dist_name}"
116+
117+
# Try the standard normalized name first
118+
val = env.get(expected_env_var)
31119
if val is not None:
32120
return val
33-
return os.environ.get(f"{tool}_{name}")
121+
122+
# If not found, search for alternative normalizations
123+
prefix = f"{tool}_{name}_FOR_"
124+
alternative_matches = _search_env_vars_with_prefix(prefix, dist_name, env)
125+
126+
if alternative_matches:
127+
# Found alternative matches - use the first one but warn
128+
env_var, value = alternative_matches[0]
129+
log.warning(
130+
"Found environment variable '%s' for dist name '%s', "
131+
"but expected '%s'. Consider using the standard normalized name.",
132+
env_var,
133+
dist_name,
134+
expected_env_var,
135+
)
136+
if len(alternative_matches) > 1:
137+
other_vars = [var for var, _ in alternative_matches[1:]]
138+
log.warning(
139+
"Multiple alternative environment variables found: %s. Using '%s'.",
140+
other_vars,
141+
env_var,
142+
)
143+
return value
144+
145+
# No exact or alternative matches found - look for potential typos
146+
close_matches = _find_close_env_var_matches(prefix, env_var_dist_name, env)
147+
if close_matches:
148+
log.warning(
149+
"Environment variable '%s' not found for dist name '%s' "
150+
"(canonicalized as '%s'). Did you mean one of these? %s",
151+
expected_env_var,
152+
dist_name,
153+
canonical_dist_name,
154+
close_matches,
155+
)
156+
157+
return generic_val
34158

35159

36160
def _read_pretended_metadata_for(

0 commit comments

Comments
 (0)