From e7ad95384d5a50a7630b2a4faa6d630811dee355 Mon Sep 17 00:00:00 2001
From: lukken <lukken@astron.nl>
Date: Wed, 15 Jun 2022 06:57:02 +0000
Subject: [PATCH] L2SS-832: Version string as global without gitpython

---
 bin/start-ds.sh                               |   5 +-
 docker-compose/jupyter/requirements.txt       |   1 -
 .../lofar-device-base/lofar-requirements.txt  |   2 +-
 tangostationcontrol/VERSION                   |   1 +
 tangostationcontrol/pyproject.toml            |   3 +
 tangostationcontrol/requirements.txt          |   4 +-
 tangostationcontrol/setup.cfg                 |   9 +-
 tangostationcontrol/setup.py                  |   7 --
 .../tangostationcontrol/__init__.py           |  11 +-
 .../common/lofar_logging.py                   |   4 +-
 .../common/lofar_version.py                   | 116 ------------------
 .../devices/lofar_device.py                   |   4 +-
 .../devices/observation_control.py            |   4 +-
 .../test/common/test_lofar_version.py         | 107 ----------------
 14 files changed, 26 insertions(+), 252 deletions(-)
 create mode 100644 tangostationcontrol/VERSION
 create mode 100644 tangostationcontrol/pyproject.toml
 delete mode 100644 tangostationcontrol/setup.py
 delete mode 100644 tangostationcontrol/tangostationcontrol/common/lofar_version.py
 delete mode 100644 tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py

diff --git a/bin/start-ds.sh b/bin/start-ds.sh
index b9b958ecd..9ae8ae900 100755
--- a/bin/start-ds.sh
+++ b/bin/start-ds.sh
@@ -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
diff --git a/docker-compose/jupyter/requirements.txt b/docker-compose/jupyter/requirements.txt
index e0116405e..27a6d1ed3 100644
--- a/docker-compose/jupyter/requirements.txt
+++ b/docker-compose/jupyter/requirements.txt
@@ -1,4 +1,3 @@
-GitPython >= 3.1.24 # BSD
 ipython >=7.27.0,!=7.28.0 # BSD
 jupyter
 ipykernel
diff --git a/docker-compose/lofar-device-base/lofar-requirements.txt b/docker-compose/lofar-device-base/lofar-requirements.txt
index 718c3a13a..d150b6a4c 100644
--- a/docker-compose/lofar-device-base/lofar-requirements.txt
+++ b/docker-compose/lofar-device-base/lofar-requirements.txt
@@ -1,2 +1,2 @@
 # Do not put tangostationcontrol dependencies here, only setup.py / __init__.py
-GitPython >= 3.1.20 # BSD
+
diff --git a/tangostationcontrol/VERSION b/tangostationcontrol/VERSION
new file mode 100644
index 000000000..ceab6e11e
--- /dev/null
+++ b/tangostationcontrol/VERSION
@@ -0,0 +1 @@
+0.1
\ No newline at end of file
diff --git a/tangostationcontrol/pyproject.toml b/tangostationcontrol/pyproject.toml
new file mode 100644
index 000000000..3588ce52b
--- /dev/null
+++ b/tangostationcontrol/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ['setuptools>=42', 'wheel']
+build-backend = 'setuptools.build_meta'
diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt
index 90ae3c178..aa00dc4a5 100644
--- a/tangostationcontrol/requirements.txt
+++ b/tangostationcontrol/requirements.txt
@@ -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
diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg
index c8e9a4390..5cf179bef 100644
--- a/tangostationcontrol/setup.cfg
+++ b/tangostationcontrol/setup.cfg
@@ -1,6 +1,6 @@
 [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=.
diff --git a/tangostationcontrol/setup.py b/tangostationcontrol/setup.py
deleted file mode 100644
index 6356812fd..000000000
--- a/tangostationcontrol/setup.py
+++ /dev/null
@@ -1,7 +0,0 @@
-import setuptools
-
-with open('requirements.txt') as f:
-    required = f.read().splitlines()
-
-# Requires: setup.cfg
-setuptools.setup(install_requires=required)
diff --git a/tangostationcontrol/tangostationcontrol/__init__.py b/tangostationcontrol/tangostationcontrol/__init__.py
index c6e48f3e8..2e6117676 100644
--- a/tangostationcontrol/tangostationcontrol/__init__.py
+++ b/tangostationcontrol/tangostationcontrol/__init__.py
@@ -1,3 +1,10 @@
-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__)
diff --git a/tangostationcontrol/tangostationcontrol/common/lofar_logging.py b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py
index bc613361d..a2c4ad391 100644
--- a/tangostationcontrol/tangostationcontrol/common/lofar_logging.py
+++ b/tangostationcontrol/tangostationcontrol/common/lofar_logging.py
@@ -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
diff --git a/tangostationcontrol/tangostationcontrol/common/lofar_version.py b/tangostationcontrol/tangostationcontrol/common/lofar_version.py
deleted file mode 100644
index 0a70002ed..000000000
--- a/tangostationcontrol/tangostationcontrol/common/lofar_version.py
+++ /dev/null
@@ -1,116 +0,0 @@
-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())
diff --git a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py
index 1ff328f78..0cc6fa0bb 100644
--- a/tangostationcontrol/tangostationcontrol/devices/lofar_device.py
+++ b/tangostationcontrol/tangostationcontrol/devices/lofar_device.py
@@ -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 = []
diff --git a/tangostationcontrol/tangostationcontrol/devices/observation_control.py b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
index cfb3f2f17..f29c2032c 100644
--- a/tangostationcontrol/tangostationcontrol/devices/observation_control.py
+++ b/tangostationcontrol/tangostationcontrol/devices/observation_control.py
@@ -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
diff --git a/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py b/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py
deleted file mode 100644
index 89ac894d9..000000000
--- a/tangostationcontrol/tangostationcontrol/test/common/test_lofar_version.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# -*- 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)
-- 
GitLab