Skip to content

Commit 3582abc

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 3582abc

File tree

2 files changed

+169
-1
lines changed

2 files changed

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

0 commit comments

Comments
 (0)