Skip to content

Commit 1f13121

Browse files
authored
[change] Releaser: defined a generic package type to use as fallback
The logic which detected OpenWrt packages has been generalized so that any file which doesn't fall under well defined categories like "python package", "npm package", "ansible role", etc., can fall under the "generic package" type, as long as a VERSION file is defined in the top level directory of the repository, the releaser tool will be able to deal with it, regardless of the tech stack used.
1 parent fba74fa commit 1f13121

5 files changed

Lines changed: 265 additions & 59 deletions

File tree

openwisp_utils/releaser/config.py

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ def get_package_type_from_setup():
2525
"package.json": "npm",
2626
"docker-compose.yml": "docker",
2727
".ansible-lint": "ansible",
28-
".luacheckrc": "openwrt",
28+
# The VERSION file check should be last to avoid false positives
29+
# with other package types
30+
"VERSION": "generic",
2931
}
3032
for filename, package_type in package_type_files.items():
3133
if os.path.exists(filename):
@@ -50,24 +52,33 @@ def detect_changelog_style(changelog_path):
5052

5153

5254
def _handle_python_version(config):
53-
"""Handles version detection for Python packages."""
55+
"""Handles version detection for Python packages.
56+
57+
Checks __init__.py first, then falls back to version.py (eg:
58+
netjsonconfig, netdiff).
59+
"""
5460
project_name = get_package_name_from_setup()
5561
if not project_name:
5662
return
5763
package_directory = project_name.replace("-", "_")
58-
init_py_path = os.path.join(package_directory, "__init__.py")
59-
if not os.path.exists(init_py_path):
60-
return
61-
with open(init_py_path, "r") as f:
62-
content = f.read()
63-
version_match = re.search(r"^VERSION\s*=\s*\((.*)\)", content, re.M)
64-
if version_match:
65-
config["version_path"] = init_py_path
66-
try:
67-
version_tuple = ast.literal_eval(f"({version_match.group(1)})")
68-
config["CURRENT_VERSION"] = list(version_tuple)
69-
except (ValueError, SyntaxError):
70-
config["CURRENT_VERSION"] = None
64+
candidate_files = [
65+
os.path.join(package_directory, "__init__.py"),
66+
os.path.join(package_directory, "version.py"),
67+
]
68+
for version_path in candidate_files:
69+
if not os.path.exists(version_path):
70+
continue
71+
with open(version_path, "r") as f:
72+
content = f.read()
73+
version_match = re.search(r"^VERSION\s*=\s*\((.*)\)", content, re.M)
74+
if version_match:
75+
config["version_path"] = version_path
76+
try:
77+
version_tuple = ast.literal_eval(f"({version_match.group(1)})")
78+
config["CURRENT_VERSION"] = list(version_tuple)
79+
except (ValueError, SyntaxError, TypeError):
80+
config["CURRENT_VERSION"] = None
81+
return
7182

7283

7384
def _handle_npm_version(config):
@@ -166,10 +177,8 @@ def _handle_ansible_version(config):
166177
config["CURRENT_VERSION"] = None
167178

168179

169-
def _handle_openwrt_version(config):
170-
"""Handles version detection for OpenWRT packages."""
171-
if not os.path.exists("VERSION"):
172-
return
180+
def _handle_generic_version(config):
181+
"""Handles version detection for packages using a root VERSION file."""
173182
with open("VERSION", "r") as f:
174183
version_str = f.read().strip()
175184
if not version_str:
@@ -197,7 +206,7 @@ def _handle_openwrt_version(config):
197206
"npm": _handle_npm_version,
198207
"docker": _handle_docker_version,
199208
"ansible": _handle_ansible_version,
200-
"openwrt": _handle_openwrt_version,
209+
"generic": _handle_generic_version,
201210
}
202211

203212

openwisp_utils/releaser/tests/conftest.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ def create_package_dir_with_version():
4141
return _create_package_dir_with_version
4242

4343

44+
def _create_package_dir_with_version_in_version_py(
45+
path: Path, name="my-test-package", version_str="VERSION = (1, 2, 3, 'final')"
46+
):
47+
"""Create a package using the netjsonconfig/netdiff version layout.
48+
49+
Unlike the standard layout, the VERSION tuple is defined in
50+
``version.py`` instead of ``__init__.py``.
51+
"""
52+
pkg_dir = path / name.replace("-", "_")
53+
pkg_dir.mkdir(exist_ok=True)
54+
(pkg_dir / "version.py").write_text(version_str)
55+
56+
57+
@pytest.fixture
58+
def create_package_dir_with_version_in_version_py():
59+
return _create_package_dir_with_version_in_version_py
60+
61+
4462
def _create_changelog(path: Path, ext="rst"):
4563
(path / f"CHANGES.{ext}").write_text("Changelog")
4664

@@ -113,6 +131,16 @@ def create_ansible_lint():
113131
return _create_ansible_lint
114132

115133

134+
def _create_luacheckrc(path: Path):
135+
"""Helper to create .luacheckrc file."""
136+
(path / ".luacheckrc").write_text("std = 'min'")
137+
138+
139+
@pytest.fixture
140+
def create_luacheckrc():
141+
return _create_luacheckrc
142+
143+
116144
def _create_ansible_version_file(path: Path, version="1.2.3"):
117145
"""Helper to create templates/openwisp2/version.py for ansible projects."""
118146
templates_dir = path / "templates" / "openwisp2"
@@ -125,18 +153,8 @@ def create_ansible_version_file():
125153
return _create_ansible_version_file
126154

127155

128-
def _create_luacheckrc(path: Path):
129-
"""Helper to create .luacheckrc file for OpenWRT agents."""
130-
(path / ".luacheckrc").write_text("std = 'min'")
131-
132-
133-
@pytest.fixture
134-
def create_luacheckrc():
135-
return _create_luacheckrc
136-
137-
138156
def _create_version_file(path: Path, version="1.2.3"):
139-
"""Helper to create VERSION file for OpenWRT agents."""
157+
"""Helper to create VERSION file for generic package type."""
140158
(path / "VERSION").write_text(version)
141159

142160

openwisp_utils/releaser/tests/test_config.py

Lines changed: 180 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,84 @@ def test_config_malformed_version_literal_eval_fails(
169169
assert config["CURRENT_VERSION"] is None
170170

171171

172+
def test_config_version_non_iterable_type_error(
173+
project_dir, create_setup_py, create_package_dir_with_version, create_changelog
174+
):
175+
"""Tests that TypeError is caught when version tuple is non-iterable.
176+
177+
For example, VERSION = (1) evaluates to an integer, and list(1) raises
178+
TypeError.
179+
"""
180+
create_setup_py(project_dir)
181+
create_package_dir_with_version(project_dir, version_str="VERSION = (1)")
182+
create_changelog(project_dir)
183+
config = load_config()
184+
# Should gracefully handle the TypeError and return None
185+
assert config["CURRENT_VERSION"] is None
186+
187+
188+
def test_python_version_detection_from_version_py_for_netjsonconfig_compatibility(
189+
project_dir,
190+
create_setup_py,
191+
create_package_dir_with_version_in_version_py,
192+
create_changelog,
193+
init_git_repo,
194+
):
195+
"""Ensure version detection supports the netjsonconfig/netdiff layout.
196+
197+
These projects define VERSION in ``version.py`` rather than in
198+
``__init__.py``, which differs from the standard project layout.
199+
"""
200+
create_setup_py(project_dir)
201+
create_package_dir_with_version_in_version_py(project_dir)
202+
create_changelog(project_dir)
203+
init_git_repo(project_dir)
204+
config = load_config()
205+
assert config["package_type"] == "python"
206+
assert config["version_path"] == "my_test_package/version.py"
207+
assert config["CURRENT_VERSION"] == [1, 2, 3, "final"]
208+
209+
210+
def test_python_version_ignores_version_py_when_init_py_defines_version(
211+
project_dir,
212+
create_setup_py,
213+
create_package_dir_with_version,
214+
create_changelog,
215+
init_git_repo,
216+
):
217+
create_setup_py(project_dir)
218+
# Create both __init__.py and version.py with VERSION
219+
pkg_dir = project_dir / "my_test_package"
220+
pkg_dir.mkdir(exist_ok=True)
221+
(pkg_dir / "__init__.py").write_text("VERSION = (1, 0, 0, 'final')")
222+
(pkg_dir / "version.py").write_text("VERSION = (2, 0, 0, 'final')")
223+
create_changelog(project_dir)
224+
init_git_repo(project_dir)
225+
config = load_config()
226+
assert config["package_type"] == "python"
227+
assert config["version_path"] == "my_test_package/__init__.py"
228+
assert config["CURRENT_VERSION"] == [1, 0, 0, "final"]
229+
230+
231+
def test_python_version_detection_for_netjsonconfig_when_init_py_lacks_version(
232+
project_dir,
233+
create_setup_py,
234+
create_changelog,
235+
init_git_repo,
236+
):
237+
create_setup_py(project_dir)
238+
pkg_dir = project_dir / "my_test_package"
239+
pkg_dir.mkdir(exist_ok=True)
240+
(pkg_dir / "__init__.py").write_text("# No version here\n")
241+
(pkg_dir / "version.py").write_text("VERSION = (3, 4, 5, 'final')")
242+
create_changelog(project_dir)
243+
init_git_repo(project_dir)
244+
config = load_config()
245+
assert config["package_type"] == "python"
246+
assert config["version_path"] == "my_test_package/version.py"
247+
assert config["CURRENT_VERSION"] == [3, 4, 5, "final"]
248+
249+
172250
# NPM Package Tests
173251
def test_npm_package_detection(
174252
project_dir, create_package_json, create_changelog, init_git_repo
@@ -354,45 +432,130 @@ def test_ansible_package_malformed_version(
354432
assert config["CURRENT_VERSION"] is None
355433

356434

357-
# OpenWRT Package Tests
358-
def test_openwrt_package_detection(
359-
project_dir, create_luacheckrc, create_version_file, create_changelog, init_git_repo
435+
# Generic Package Tests
436+
def test_generic_package_detection(
437+
project_dir, create_version_file, create_changelog, init_git_repo
360438
):
361-
"""Tests that openwrt package type is detected when .luacheckrc exists."""
362-
create_luacheckrc(project_dir)
439+
"""Tests that generic package type is detected when VERSION file exists."""
363440
create_version_file(project_dir, version="1.2.3")
364441
create_changelog(project_dir)
365442
init_git_repo(project_dir)
366443
config = load_config()
367-
assert config["package_type"] == "openwrt"
444+
assert config["package_type"] == "generic"
368445
assert config["version_path"] == "VERSION"
369446
assert config["CURRENT_VERSION"] == [1, 2, 3, "final"]
370447

371448

372-
def test_openwrt_without_version_file(
373-
project_dir, create_luacheckrc, create_changelog, init_git_repo
449+
def test_generic_fallback_without_other_config(
450+
project_dir, create_version_file, create_changelog, init_git_repo
374451
):
375-
"""Tests openwrt package without VERSION file."""
376-
create_luacheckrc(project_dir)
452+
"""Tests that VERSION file is used as fallback when no other package type is detected."""
453+
create_version_file(project_dir, version="2.0.1")
377454
create_changelog(project_dir)
378455
init_git_repo(project_dir)
379456
config = load_config()
380-
assert config["package_type"] == "openwrt"
381-
assert config["version_path"] is None
457+
assert config["package_type"] == "generic"
458+
assert config["version_path"] == "VERSION"
459+
assert config["CURRENT_VERSION"] == [2, 0, 1, "final"]
460+
461+
462+
def test_generic_invalid_version(
463+
project_dir, create_version_file, create_changelog, init_git_repo
464+
):
465+
"""Tests generic package with invalid version format gracefully."""
466+
create_version_file(project_dir, version="1.2") # Invalid: only 2 parts
467+
create_changelog(project_dir)
468+
init_git_repo(project_dir)
469+
config = load_config()
470+
assert config["package_type"] == "generic"
471+
assert config["version_path"] == "VERSION"
382472
assert config["CURRENT_VERSION"] is None
383473

384474

385-
def test_openwrt_invalid_version(
475+
def test_generic_not_fallback_when_other_type_detected(
476+
project_dir,
477+
create_setup_py,
478+
create_package_dir_with_version,
479+
create_version_file,
480+
create_changelog,
481+
init_git_repo,
482+
):
483+
"""Tests that VERSION file is not used as fallback when another package type is detected."""
484+
create_setup_py(project_dir)
485+
create_package_dir_with_version(project_dir)
486+
create_version_file(project_dir, version="1.2.3")
487+
create_changelog(project_dir)
488+
init_git_repo(project_dir)
489+
config = load_config()
490+
assert config["package_type"] == "python"
491+
assert config["version_path"] == "my_test_package/__init__.py"
492+
493+
494+
def test_generic_not_fallback_when_npm_detected(
495+
project_dir,
496+
create_package_json,
497+
create_version_file,
498+
create_changelog,
499+
init_git_repo,
500+
):
501+
"""Tests that VERSION file is not used as fallback when npm package is detected."""
502+
create_package_json(project_dir, version="1.2.3")
503+
create_version_file(project_dir, version="1.5.0")
504+
create_changelog(project_dir)
505+
init_git_repo(project_dir)
506+
config = load_config()
507+
assert config["package_type"] == "npm"
508+
assert config["version_path"] == "package.json"
509+
510+
511+
def test_generic_not_fallback_when_docker_detected(
512+
project_dir,
513+
create_docker_compose,
514+
create_docker_version_file,
515+
create_version_file,
516+
create_changelog,
517+
init_git_repo,
518+
):
519+
"""Tests that VERSION file is not used as fallback when docker package is detected."""
520+
create_docker_compose(project_dir)
521+
create_docker_version_file(project_dir, version="1.2.3")
522+
create_version_file(project_dir, version="1.5.0")
523+
create_changelog(project_dir)
524+
init_git_repo(project_dir)
525+
config = load_config()
526+
assert config["package_type"] == "docker"
527+
assert config["version_path"] == DOCKER_VERSION_PATH
528+
529+
530+
def test_generic_not_fallback_when_ansible_detected(
531+
project_dir,
532+
create_ansible_lint,
533+
create_ansible_version_file,
534+
create_version_file,
535+
create_changelog,
536+
init_git_repo,
537+
):
538+
"""Tests that VERSION file is not used as fallback when ansible package is detected."""
539+
create_ansible_lint(project_dir)
540+
create_ansible_version_file(project_dir, version="1.2.3")
541+
create_version_file(project_dir, version="1.5.0")
542+
create_changelog(project_dir)
543+
init_git_repo(project_dir)
544+
config = load_config()
545+
assert config["package_type"] == "ansible"
546+
assert config["version_path"] == "templates/openwisp2/version.py"
547+
548+
549+
def test_luacheckrc_does_not_trigger_detection(
386550
project_dir, create_luacheckrc, create_changelog, init_git_repo
387551
):
388-
"""Tests openwrt package with invalid version format gracefully."""
552+
"""Tests that .luacheckrc alone does not trigger any package type detection."""
389553
create_luacheckrc(project_dir)
390-
(project_dir / "VERSION").write_text("1.2") # Invalid: only 2 parts
391554
create_changelog(project_dir)
392555
init_git_repo(project_dir)
393556
config = load_config()
394-
assert config["package_type"] == "openwrt"
395-
assert config["version_path"] == "VERSION"
557+
assert config["package_type"] is None
558+
assert config["version_path"] is None
396559
assert config["CURRENT_VERSION"] is None
397560

398561

0 commit comments

Comments
 (0)