Skip to content

Commit 02c1588

Browse files
committed
Create accepts minor versions
When create is passed a minor version it will default to the latest e.g. 3.10 will default to the latest known 3.10 version.
1 parent 57caeb0 commit 02c1588

File tree

2 files changed

+160
-1
lines changed

2 files changed

+160
-1
lines changed

relenv/create.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
format_shebang,
2424
relative_interpreter,
2525
)
26+
from .pyversions import Version, python_versions
2627

2728

2829
@contextlib.contextmanager
@@ -251,8 +252,32 @@ def main(args: argparse.Namespace) -> None:
251252
print(
252253
"Warning: Cross compilation support is experimental and is not fully tested or working!"
253254
)
255+
256+
# Resolve version (support minor version like "3.12" or full version like "3.12.7")
257+
requested = Version(args.python)
258+
259+
if requested.micro:
260+
# Full version specified (e.g., "3.12.7")
261+
pyversions = python_versions()
262+
if requested not in pyversions:
263+
print(f"Unknown version {requested}")
264+
strversions = "\n".join([str(_) for _ in pyversions])
265+
print(f"Known versions are:\n{strversions}")
266+
sys.exit(1)
267+
create_version = requested
268+
else:
269+
# Minor version specified (e.g., "3.12"), resolve to latest
270+
pyversions = python_versions(args.python)
271+
if not pyversions:
272+
print(f"Unknown minor version {requested}")
273+
all_versions = python_versions()
274+
strversions = "\n".join([str(_) for _ in all_versions])
275+
print(f"Known versions are:\n{strversions}")
276+
sys.exit(1)
277+
create_version = sorted(list(pyversions.keys()))[-1]
278+
254279
try:
255-
create(name, arch=args.arch, version=args.python)
280+
create(name, arch=args.arch, version=str(create_version))
256281
except CreateException as exc:
257282
print(exc)
258283
sys.exit(1)

tests/test_create.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,137 @@ def test_create_arches_directory_exists(tmp_path: pathlib.Path) -> None:
5252
with patch("relenv.create.arches", mocked_arches):
5353
with pytest.raises(CreateException):
5454
create("foo", dest=tmp_path)
55+
56+
57+
def test_create_with_minor_version(tmp_path: pathlib.Path) -> None:
58+
"""Test that minor version (e.g., '3.12') resolves to latest micro version."""
59+
import argparse
60+
61+
from relenv.create import main
62+
from relenv.pyversions import Version
63+
64+
# Mock python_versions to return some test versions
65+
all_versions = {
66+
Version("3.11.5"): "aaa111",
67+
Version("3.12.5"): "abc123",
68+
Version("3.12.6"): "def456",
69+
Version("3.12.7"): "ghi789",
70+
Version("3.13.1"): "zzz999",
71+
}
72+
73+
def mock_python_versions(minor: str | None = None) -> dict[Version, str]:
74+
"""Mock that filters versions by minor version like the real function."""
75+
if minor is None:
76+
return all_versions
77+
# Filter versions matching the minor version
78+
mv = Version(minor)
79+
return {
80+
v: h
81+
for v, h in all_versions.items()
82+
if v.major == mv.major and v.minor == mv.minor
83+
}
84+
85+
# Create a fake archive
86+
to_be_archived = tmp_path / "to_be_archived"
87+
to_be_archived.mkdir()
88+
test_file = to_be_archived / "testfile"
89+
test_file.touch()
90+
tar_file = tmp_path / "fake_archive"
91+
with tarfile.open(str(tar_file), "w:xz") as tar:
92+
tar.add(str(to_be_archived), to_be_archived.name)
93+
94+
args = argparse.Namespace(name="test_env", arch="x86_64", python="3.12")
95+
96+
with chdir(str(tmp_path)):
97+
with patch("relenv.create.python_versions", side_effect=mock_python_versions):
98+
with patch("relenv.create.archived_build", return_value=tar_file):
99+
with patch("relenv.create.build_arch", return_value="x86_64"):
100+
main(args)
101+
102+
to_dir = tmp_path / "test_env"
103+
assert to_dir.exists()
104+
105+
106+
def test_create_with_full_version(tmp_path: pathlib.Path) -> None:
107+
"""Test that full version (e.g., '3.12.7') still works."""
108+
import argparse
109+
110+
from relenv.create import main
111+
from relenv.pyversions import Version
112+
113+
# Mock python_versions to return some test versions
114+
all_versions = {
115+
Version("3.11.5"): "aaa111",
116+
Version("3.12.5"): "abc123",
117+
Version("3.12.6"): "def456",
118+
Version("3.12.7"): "ghi789",
119+
Version("3.13.1"): "zzz999",
120+
}
121+
122+
def mock_python_versions(minor: str | None = None) -> dict[Version, str]:
123+
"""Mock that filters versions by minor version like the real function."""
124+
if minor is None:
125+
return all_versions
126+
# Filter versions matching the minor version
127+
mv = Version(minor)
128+
return {
129+
v: h
130+
for v, h in all_versions.items()
131+
if v.major == mv.major and v.minor == mv.minor
132+
}
133+
134+
# Create a fake archive
135+
to_be_archived = tmp_path / "to_be_archived"
136+
to_be_archived.mkdir()
137+
test_file = to_be_archived / "testfile"
138+
test_file.touch()
139+
tar_file = tmp_path / "fake_archive"
140+
with tarfile.open(str(tar_file), "w:xz") as tar:
141+
tar.add(str(to_be_archived), to_be_archived.name)
142+
143+
args = argparse.Namespace(name="test_env", arch="x86_64", python="3.12.7")
144+
145+
with chdir(str(tmp_path)):
146+
with patch("relenv.create.python_versions", side_effect=mock_python_versions):
147+
with patch("relenv.create.archived_build", return_value=tar_file):
148+
with patch("relenv.create.build_arch", return_value="x86_64"):
149+
main(args)
150+
151+
to_dir = tmp_path / "test_env"
152+
assert to_dir.exists()
153+
154+
155+
def test_create_with_unknown_minor_version(tmp_path: pathlib.Path) -> None:
156+
"""Test that unknown minor version produces an error."""
157+
import argparse
158+
159+
from relenv.create import main
160+
from relenv.pyversions import Version
161+
162+
# Mock python_versions to return empty dict for unknown version
163+
all_versions = {
164+
Version("3.11.5"): "aaa111",
165+
Version("3.12.5"): "abc123",
166+
Version("3.12.6"): "def456",
167+
Version("3.12.7"): "ghi789",
168+
Version("3.13.1"): "zzz999",
169+
}
170+
171+
args = argparse.Namespace(name="test_env", arch="x86_64", python="3.99")
172+
173+
def mock_python_versions(minor: str | None = None) -> dict[Version, str]:
174+
"""Mock that filters versions by minor version like the real function."""
175+
if minor is None:
176+
return all_versions
177+
# Filter versions matching the minor version
178+
mv = Version(minor)
179+
return {
180+
v: h
181+
for v, h in all_versions.items()
182+
if v.major == mv.major and v.minor == mv.minor
183+
}
184+
185+
with patch("relenv.create.python_versions", side_effect=mock_python_versions):
186+
with patch("relenv.create.build_arch", return_value="x86_64"):
187+
with pytest.raises(SystemExit):
188+
main(args)

0 commit comments

Comments
 (0)