Skip to content
Snippets Groups Projects
Commit e7ad9538 authored by Corné Lukken's avatar Corné Lukken
Browse files

L2SS-832: Version string as global without gitpython

parent 445e419e
No related branches found
No related tags found
1 merge request!364Resolve L2SS-832
Showing
with 26 additions and 252 deletions
......@@ -32,10 +32,7 @@ if [[ $TANGOSTATIONCONTROL ]]; then
else
# Install the package, exit 1 if it fails
cd tangostationcontrol || exit 1
mkdir -p /tmp/tangostationcontrol
python3 setup.py build --build-base /tmp/tangostationcontrol egg_info --egg-base /tmp/tangostationcontrol bdist_wheel --dist-dir /tmp/tangostationcontrol || exit 1
# shellcheck disable=SC2012
pip install "$(ls -Art /tmp/tangostationcontrol/*.whl | tail -n 1)"
pip install ./
fi
# Return to the stored the directory, this preserves the working_dir argument in
......
GitPython >= 3.1.24 # BSD
ipython >=7.27.0,!=7.28.0 # BSD
jupyter
ipykernel
......
# Do not put tangostationcontrol dependencies here, only setup.py / __init__.py
GitPython >= 3.1.20 # BSD
0.1
\ No newline at end of file
[build-system]
requires = ['setuptools>=42', 'wheel']
build-backend = 'setuptools.build_meta'
......@@ -13,7 +13,5 @@ docker >= 5.0.3 # Apache 2
python-logstash-async >= 2.3.0 # MIT
python-casacore >= 3.3.1 # LGPLv3
etrs-itrs@git+https://github.com/brentjens/etrs-itrs # Apache 2
# numpy must be manually added even though etrs-itrs requires it
numpy >= 1.21.0 # BSD
lofarantpos >= 0.5.0 # Apache 2
python-geohash >= 0.8.5 # Apache 2MIT
python-geohash >= 0.8.5 # Apache 2 / MIT
[metadata]
name = tangostationcontrol
version = attr: tangostationcontrol.__version__
version = file: VERSION
summary = LOFAR 2.0 Station Control
description_file =
README.md
......@@ -13,14 +13,12 @@ project_urls =
license = Apache-2
classifier =
Environment :: Console
Development Status :: 3 - Alpha
License :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
[options]
package_dir=
......@@ -28,7 +26,8 @@ package_dir=
packages=find:
python_requires => 3.7
install_requires =
GitPython>=3.1.20
importlib-metadata>=0.12;python_version<"3.8"
pip>=1.5
[options.packages.find]
where=.
......
import setuptools
with open('requirements.txt') as f:
required = f.read().splitlines()
# Requires: setup.cfg
setuptools.setup(install_requires=required)
from tangostationcontrol.common.lofar_version import get_version
try:
from importlib import metadata
except ImportError: # for Python<3.8
import importlib_metadata as metadata
__version__ = get_version()
__version__ = metadata.version("tangostationcontrol")
def print_version(*args, **kwargs):
print(__version__)
......@@ -5,7 +5,7 @@ import traceback
import socket
import time
from .lofar_version import get_version
from tangostationcontrol import __version__ as version
class TangoLoggingHandler(logging.Handler):
LEVEL_TO_DEVICE_STREAM = {
......@@ -97,7 +97,7 @@ class LogAnnotator(logging.Formatter):
record.lofar_id = f"tango - {record.tango_device}"
# annotate record with the current software version
record.software_version = get_version()
record.software_version = version
# we just annotate, we don't filter
return True
......
import git
import os
import functools
import pkg_resources
import re
basepath = os.path.dirname(os.path.abspath(__file__))
def get_repo(starting_directory: str = basepath, limit = 10) -> git.Repo:
""" Try finding the repository by traversing up the tree.
By default, the repository containing this module is returned.
"""
directory = starting_directory
try:
return git.Repo(directory)
except git.InvalidGitRepositoryError:
pass
# We now have to traverse up the tree up until limit diretories
for _i in range(limit):
if directory == "/" or not os.path.exists(directory):
break
directory = os.path.abspath(directory + os.path.sep + "..")
try:
return git.Repo(directory)
except git.InvalidGitRepositoryError:
pass
# Could not find a repo within the limit so return None
return None
@functools.lru_cache(maxsize=None)
def get_version(repo: git.Repo = None) -> str:
""" Return a version string for the current commit.
There is a practical issue: the repository changes over time, f.e. switching branches with 'git checkout'. We want
to know the version that is running in memory, not the one that is on disk.
As a work-around, we cache the version information, in that it is at least consistent. It is up to the caller
to request the version early enough.
The version string is of the following pattern:
- ${MAJOR}.${MINOR}.${PATCH}[.${BRANCH}$.{COMMIT}][.dirty]
For releases only ${MAJOR}.${MINOR}.${PATCH} should be set. Versioning is
achieved by tagging commits using the `v${MAJOR}.${MINOR}.${PATCH}` pattern.
The leading `v` is none optional!
"""
if repo is None:
repo = get_repo()
# When we can't find a git repo anymore, we must be packaged. Extract the
# package version directly
if repo is None:
try:
return pkg_resources.require("tangostationcontrol")[0].version
except Exception:
pass
# Filter all tags so that they must match vMAJOR.MINOR.PATCH or
# vMAJOR.MINOR.PATCH.BRANCHCOMMIT
reg = re.compile(r'^v[0-9](\.[0-9]){2}(\.[a-z]*[0-9]*)?')
commit = repo.commit()
filtered_tags = [tag.name for tag in repo.tags if reg.search(tag.name)]
# Order tags from newest to oldest
tags = _order_tags(repo, filtered_tags)
# Find closest tag for commit
closest_tag = _find_closest_tag(commit, repo, tags)
if commit in tags:
# a tag = production ready
commit_str = "{}".format(tags[commit].name[1:])
elif repo.head.is_detached:
# no active branch
commit_str = "{}.{}".format(closest_tag.name[1:], commit)
else:
# HEAD of a branch
branch = repo.active_branch
commit_str = "{}.{}.{}".format(closest_tag.name[1:], branch, commit)
return "{}{}".format(commit_str, ".dirty" if repo.is_dirty() else "")
def _order_tags(repo, filtered_tags):
""" Helper function to order tags from newest to oldest """
return {tag.commit: tag for tag in reversed(repo.tags) if tag.name in filtered_tags}
def _find_closest_tag(commit, repo, tags):
""" Helper function to find closest tag for commit """
closest_tag = type('',(object,),{"name": 'v0.0.0'})()
for item in commit.iter_items(repo, commit):
if item.type == 'commit' and item in tags:
closest_tag = tags[item]
break
return closest_tag
# at least cache the current repo version immediately
try:
_ = get_version()
except Exception:
"B001 Do not use bare `except:`, it also catches unexpected events like"
"memory errors, interrupts, system exit"
pass
def main(args=None, **kwargs):
print(get_version())
......@@ -20,9 +20,9 @@ import numpy
import textwrap
# Additional import
from tangostationcontrol import __version__ as version
from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper
from tangostationcontrol.common.lofar_logging import log_exceptions
from tangostationcontrol.common.lofar_version import get_version
from tangostationcontrol.devices.device_decorators import only_in_states, fault_on_error
from tangostationcontrol.toolkit.archiver import Archiver
......@@ -75,7 +75,7 @@ class lofar_device(Device, metaclass=DeviceMeta):
# Attributes
# ----------
version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: get_version())
version_R = attribute(dtype=str, access=AttrWriteType.READ, fget=lambda self: version)
# list of translator property names to be set by set_translator_defaults
TRANSLATOR_DEFAULT_SETTINGS = []
......
......@@ -15,9 +15,9 @@ from tango import Except, DevFailed, DevState, AttrWriteType, DebugIt, DevicePro
from tango.server import Device, command, attribute
from tango import EventType
from tangostationcontrol import __version__ as version
from tangostationcontrol.common.entrypoint import entry
from tangostationcontrol.common.lofar_logging import device_logging_to_python, log_exceptions
from tangostationcontrol.common.lofar_version import get_version
from tangostationcontrol.devices.device_decorators import only_when_on, fault_on_error
from tangostationcontrol.devices.lofar_device import lofar_device
from tangostationcontrol.devices.observation import Observation
......@@ -71,7 +71,7 @@ class ObservationControl(lofar_device):
- string version
"""
# Attributes
version_R = attribute(dtype = str, access = AttrWriteType.READ, fget = lambda self: get_version())
version_R = attribute(dtype = str, access = AttrWriteType.READ, fget = lambda self: version)
running_observations_R = attribute(dtype = (numpy.int64, ), access = AttrWriteType.READ)
# Core functions
......
# -*- coding: utf-8 -*-
#
# This file is part of the LOFAR 2.0 Station Software
#
#
#
# Distributed under the terms of the APACHE license.
# See LICENSE.txt for more info.
import git
from unittest import mock
from tangostationcontrol.common import lofar_version
from tangostationcontrol.test import base
class TestLofarVersion(base.TestCase):
def setUp(self):
super(TestLofarVersion, self).setUp()
# Clear the cache as this function of lofar_version uses LRU decorator
# This is a good demonstration of how unit tests in Python can have
# permanent effects, typically fixtures are needed to restore these.
lofar_version.get_version.cache_clear()
def test_get_version(self):
"""Test if attributes of get_repo are correctly used by get_version"""
with mock.patch.object(lofar_version, 'get_repo') as m_get_repo:
m_commit = mock.Mock()
m_commit.return_value.__str__ = mock.Mock(return_value="123456")
m_commit.return_value.iter_items.return_value = []
m_is_dirty = mock.Mock()
m_is_dirty.return_value = False
m_head = mock.Mock(is_detached=False)
m_get_repo.return_value = mock.Mock(
active_branch="main", commit=m_commit, tags=[],
is_dirty=m_is_dirty, head=m_head)
# No need for special string equal in Python
self.assertEqual("0.0.0.main.123456", lofar_version.get_version())
def test_get_version_tag(self):
"""Test if get_version determines production_ready for tagged commit"""
with mock.patch.object(lofar_version, 'get_repo') as m_get_repo:
m_commit = mock.Mock()
m_commit.return_value.__str__ = mock.Mock(return_value="123456")
m_commit.return_value.iter_items.return_value = []
m_is_dirty = mock.Mock()
m_is_dirty.return_value = False
m_head = mock.Mock(is_detached=False)
m_tag_commit = mock.Mock(type="commit")
m_tag_commit.__str__ = mock.Mock(return_value="123456")
m_tag = mock.Mock(commit=m_tag_commit)
m_tag.name = "v0.0.3"
m_tag.__str__ = mock.Mock(return_value= "v0.0.3")
m_commit.return_value = m_tag_commit
m_commit.return_value.iter_items.return_value = [m_tag_commit]
m_get_repo.return_value = mock.Mock(
active_branch="main", commit=m_commit,
tags=[m_tag], is_dirty=m_is_dirty, head=m_head)
self.assertEqual("0.0.3", lofar_version.get_version())
@mock.patch.object(lofar_version, 'get_repo')
def test_get_version_tag_dirty(self, m_get_repo):
"""Test if get_version determines dirty tagged commit"""
m_commit = mock.Mock()
m_commit.return_value.__str__ = mock.Mock(return_value="123456")
m_commit.return_value.iter_items.return_value = []
m_is_dirty = mock.Mock()
m_is_dirty.return_value = True
m_head = mock.Mock(is_detached=False)
m_get_repo.return_value = mock.Mock(
active_branch="main", commit=m_commit, tags=[],
is_dirty=m_is_dirty, head=m_head)
# No need for special string equal in Python
self.assertEqual("0.0.0.main.123456.dirty", lofar_version.get_version())
def test_catch_repo_error(self):
"""Test if invalid git directories will raise error"""
with mock.patch.object(lofar_version, 'get_repo') as m_get_repo:
# Configure lofar_version.get_repo to raise InvalidGitRepositoryError
m_get_repo.side_effect = git.InvalidGitRepositoryError
# Test that error is raised by get_version
self.assertRaises(
git.InvalidGitRepositoryError, lofar_version.get_version)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment