Skip to content

Commit e95d3bf

Browse files
committed
Fix version endpoint in packaged image
1 parent f06bdc4 commit e95d3bf

4 files changed

Lines changed: 140 additions & 28 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ extend_skip = []
9494
# pytest configuration
9595
[tool.pytest.ini_options]
9696
minversion = "6.2.5"
97-
pythonpath = ["."]
97+
pythonpath = ["src", "."]
9898

9999

100100
# Options
@@ -122,7 +122,7 @@ norecursedirs = [
122122
"lib",
123123
"lib64",
124124
]
125-
testpaths = ["test"]
125+
testpaths = ["tests"]
126126

127127

128128
# Markers

src/nodenorm/handlers/version.py

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,13 @@
1-
import logging
2-
import pathlib
3-
import git
1+
import asyncio
42

53
from biothings.web.handlers import BaseHandler
64

7-
logger = logging.getLogger(__name__)
5+
from nodenorm.version import get_version
86

97

108
class VersionHandler(BaseHandler):
119
name = "version"
1210

13-
def get_github_commit_hash(self):
14-
"""Retrieve the current GitHub commit hash using gitpython."""
15-
try:
16-
# Resolve the absolute path to the current file
17-
file_path = pathlib.Path(__file__).resolve()
18-
19-
# Use git.Repo to find the root of the repository
20-
repo = git.Repo(file_path, search_parent_directories=True)
21-
22-
if repo.bare:
23-
# Get the absolute path to the repository root
24-
repo_dir = repo.working_tree_dir
25-
logger.error(f"Git repository not found in directory: {repo_dir}")
26-
return "Unknown"
27-
28-
commit_hash = repo.head.commit.hexsha # Get the latest commit hash
29-
return commit_hash
30-
except Exception as e:
31-
logger.error(f"Error getting GitHub commit hash: {e}")
32-
return "Unknown"
33-
3411
async def get(self, *args, **kwargs):
35-
version = self.get_github_commit_hash()
12+
version = await asyncio.to_thread(get_version)
3613
self.write({"version": version})

src/nodenorm/version.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import logging
2+
import os
3+
import pathlib
4+
from collections.abc import Iterable
5+
from functools import cache
6+
7+
from git import InvalidGitRepositoryError, NoSuchPathError, Repo
8+
9+
logger = logging.getLogger(__name__)
10+
11+
UNKNOWN_VERSION = "Unknown"
12+
VERSION_FILE_NAME = "version.txt"
13+
VERSION_FILE_ENV_VAR = "NODENORM_VERSION_FILE"
14+
CONTAINER_VERSION_FILE = pathlib.Path("/home/nodenorm/configuration") / VERSION_FILE_NAME
15+
16+
17+
def read_version_file(version_file_paths: Iterable[pathlib.Path] | None = None) -> str | None:
18+
"""Read the build-time version file when the app is running from a packaged image."""
19+
candidate_paths = []
20+
configured_path = os.getenv(VERSION_FILE_ENV_VAR)
21+
if configured_path:
22+
candidate_paths.append(pathlib.Path(configured_path))
23+
24+
if version_file_paths is None:
25+
candidate_paths.extend([pathlib.Path.cwd() / VERSION_FILE_NAME, CONTAINER_VERSION_FILE])
26+
else:
27+
candidate_paths.extend(version_file_paths)
28+
29+
for version_file_path in candidate_paths:
30+
try:
31+
version = version_file_path.read_text(encoding="utf-8").strip()
32+
except OSError:
33+
continue
34+
35+
if version:
36+
return version
37+
38+
return None
39+
40+
41+
@cache
42+
def get_github_commit_hash(source_path: pathlib.Path | None = None) -> str:
43+
"""Retrieve the current GitHub commit hash using gitpython."""
44+
try:
45+
repo_path = source_path or pathlib.Path(__file__).resolve()
46+
repo = Repo(repo_path, search_parent_directories=True)
47+
48+
return repo.head.commit.hexsha
49+
except (InvalidGitRepositoryError, NoSuchPathError) as exc:
50+
logger.warning("Git repository unavailable for version lookup: %s", exc)
51+
return UNKNOWN_VERSION
52+
except Exception as exc:
53+
logger.exception("Error getting GitHub commit hash: %s", exc)
54+
return UNKNOWN_VERSION
55+
56+
57+
@cache
58+
def get_version() -> str:
59+
return read_version_file() or get_github_commit_hash()

tests/test_version.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import pytest
2+
3+
from nodenorm import version as nodenorm_version
4+
5+
6+
@pytest.fixture(autouse=True)
7+
def clear_version_caches():
8+
nodenorm_version.get_github_commit_hash.cache_clear()
9+
nodenorm_version.get_version.cache_clear()
10+
yield
11+
nodenorm_version.get_github_commit_hash.cache_clear()
12+
nodenorm_version.get_version.cache_clear()
13+
14+
15+
def test_read_version_file_uses_configured_path(tmp_path, monkeypatch):
16+
configured_version_file = tmp_path / "configured-version.txt"
17+
configured_version_file.write_text("configured-sha\n", encoding="utf-8")
18+
fallback_version_file = tmp_path / "version.txt"
19+
fallback_version_file.write_text("fallback-sha\n", encoding="utf-8")
20+
21+
monkeypatch.setenv(nodenorm_version.VERSION_FILE_ENV_VAR, str(configured_version_file))
22+
23+
assert nodenorm_version.read_version_file([fallback_version_file]) == "configured-sha"
24+
25+
26+
def test_read_version_file_ignores_missing_and_blank_files(tmp_path, monkeypatch):
27+
blank_version_file = tmp_path / "blank-version.txt"
28+
blank_version_file.write_text("\n", encoding="utf-8")
29+
version_file = tmp_path / "version.txt"
30+
version_file.write_text("build-sha\n", encoding="utf-8")
31+
32+
monkeypatch.delenv(nodenorm_version.VERSION_FILE_ENV_VAR, raising=False)
33+
34+
assert (
35+
nodenorm_version.read_version_file([tmp_path / "missing.txt", blank_version_file, version_file])
36+
== "build-sha"
37+
)
38+
39+
40+
def test_read_version_file_allows_empty_fallback_paths(monkeypatch):
41+
monkeypatch.delenv(nodenorm_version.VERSION_FILE_ENV_VAR, raising=False)
42+
43+
assert nodenorm_version.read_version_file([]) is None
44+
45+
46+
def test_get_version_prefers_build_version_file(monkeypatch):
47+
monkeypatch.setattr(nodenorm_version, "read_version_file", lambda: "build-sha")
48+
monkeypatch.setattr(
49+
nodenorm_version,
50+
"get_github_commit_hash",
51+
lambda: pytest.fail("Git fallback should not run when a build version file exists"),
52+
)
53+
54+
assert nodenorm_version.get_version() == "build-sha"
55+
56+
57+
def test_get_version_falls_back_to_git(monkeypatch):
58+
monkeypatch.setattr(nodenorm_version, "read_version_file", lambda: None)
59+
monkeypatch.setattr(nodenorm_version, "get_github_commit_hash", lambda: "git-sha")
60+
61+
assert nodenorm_version.get_version() == "git-sha"
62+
63+
64+
def test_get_version_caches_resolved_version(monkeypatch):
65+
calls = 0
66+
67+
def read_version_file():
68+
nonlocal calls
69+
calls += 1
70+
return "build-sha"
71+
72+
monkeypatch.setattr(nodenorm_version, "read_version_file", read_version_file)
73+
74+
assert nodenorm_version.get_version() == "build-sha"
75+
assert nodenorm_version.get_version() == "build-sha"
76+
assert calls == 1

0 commit comments

Comments
 (0)