diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6a54e00f52498c5afc4c7080ca82aeb42b4fe59c..d4d4a687213e9fbd3fa2c0b55193f66d62d0836c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,7 @@
 default:
-  image: $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG
+  image:
+    name: $CI_REGISTRY_IMAGE/ci-build-runner:$CI_COMMIT_REF_SLUG
+    pull_policy: always
   before_script:
     - python --version # For debugging
   cache:
@@ -10,8 +12,6 @@ default:
 stages:
   - prepare
   - lint
-  # check if this needs to be a separate step
-  # - build_extensions
   - test
   - package
   - images
@@ -52,11 +52,6 @@ run_pylint:
     - tox -e pylint
   allow_failure: true
 
-# build_extensions:
-#   stage: build_extensions
-#   script:
-#     - echo "build fortran/c/cpp extension source code"
-
 sast:
   variables:
     SAST_EXCLUDED_ANALYZERS: brakeman, flawfinder, kubesec, nodejs-scan, phpcs-security-audit,
@@ -76,6 +71,10 @@ secret_detection:
 # Basic setup for all Python versions for which we don't have a base image
 .run_unit_test_version_base:
   before_script:
+    - apt update
+    - apt install -y postgresql
+    - ln -sf /usr/lib/postgresql/15/bin/initdb /usr/bin/initdb
+    - useradd -m tests
     - python --version # For debugging
     - python -m pip install --upgrade pip
     - python -m pip install --upgrade tox twine
@@ -86,10 +85,10 @@ run_unit_tests:
   stage: test
   image: python:3.${PY_VERSION}
   script:
-    - tox -e py3${PY_VERSION}
+    - su tests -c "tox -e py3${PY_VERSION}"
   parallel:
     matrix: # use the matrix for testing
-      - PY_VERSION: [8, 9, 10, 11]
+      - PY_VERSION: [10, 11, 12]
 
 # Run code coverage on the base image thus also performing unit tests
 run_unit_tests_coverage:
@@ -114,15 +113,6 @@ package_files:
   script:
     - tox -e build
 
-package_docs:
-  stage: package
-  artifacts:
-    expire_in: 1w
-    paths:
-      - docs/build/*
-  script:
-    - tox -e docs
-
 docker_build:
   stage: images
   image: docker:latest
@@ -134,8 +124,7 @@ docker_build:
   script:
     - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
     - docker build -f docker/pycommon/Dockerfile . --build-arg BUILD_ENV=copy --tag $CI_REGISTRY_IMAGE/pycommon:$CI_COMMIT_REF_SLUG
-    # enable this push line once you have configured docker registry cleanup policy
-    # - docker push $CI_REGISTRY_IMAGE/pycommon:$CI_COMMIT_REF_SLUG
+    - docker push $CI_REGISTRY_IMAGE/pycommon:$CI_COMMIT_REF_SLUG
 
 run_integration_tests:
   stage: integration
@@ -200,19 +189,6 @@ publish_on_pypi:
     #   --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi dist/*
     - exit 1
 
-publish_to_readthedocs:
-  stage: publish
-  allow_failure: true
-  environment: readthedocs
-  needs:
-    - package_docs
-  when: manual
-  rules:
-    - if: '$CI_COMMIT_TAG && $CI_COMMIT_REF_PROTECTED == "true"'
-  script:
-    - echo "scp docs/* ???"
-    - exit 1
-
 release_job:
   stage: publish
   image: registry.gitlab.com/gitlab-org/release-cli:latest
diff --git a/README.md b/README.md
index e2d390da4f46b07269e8d296ffabc68fa30e2006..e975581fa1126777f339ddc276e7f57cb03eeb63 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
 # PyCommon
 
-![Build status](git.astron.nl/tmss/libraries/pycommon/badges/main/pipeline.svg)
-![Test coverage](git.astron.nl/tmss/libraries/pycommon/badges/main/coverage.svg)
-<!-- ![Latest release](https://git.astron.nl/templates/python-package/badges/main/release.svg) -->
+![Build status](git.astron.nl/tmss/libraries/pycommon/badges/master/pipeline.svg)
+![Test coverage](git.astron.nl/tmss/libraries/pycommon/badges/master/coverage.svg)
+![Latest release](https://git.astron.nl/templates/python-package/badges/master/release.svg)
 
-An example repository of an CI/CD pipeline for building, testing and publishing a python package.
+Common Python library for TMSS backend
 
 ## Installation
 ```
@@ -22,13 +22,6 @@ the CI/CD pipelines. And you can consider creating protected version tags for so
 Once the cleanup policy for docker registry is setup you can uncomment the `docker push` comment in the `.gitlab-ci.yml`
 file from the `docker_build` job. This will allow to download minimal docker images with your Python package installed.
 
-## Usage
-```python
-from pycommon import cool_module
-
-cool_module.greeter()   # prints "Hello World"
-```
-
 ## Contributing
 
 To contribute, please create a feature branch and a "Draft" merge request.
@@ -50,4 +43,4 @@ To automatically apply most suggested linting changes execute:
 ```tox -e format```
 
 ## License
-This project is licensed under the Apache License Version 2.0
+This project is licensed under GPLv3
diff --git a/docker/ci-runner/Dockerfile b/docker/ci-runner/Dockerfile
index 6cb46437656aad64d8292c0a10f6edaffffba8e3..874c123789d522e718161a9c7b2215c2a2138921 100644
--- a/docker/ci-runner/Dockerfile
+++ b/docker/ci-runner/Dockerfile
@@ -1,4 +1,14 @@
-FROM python:3.12
+FROM python:3.13
+
+RUN --mount=type=cache,target=/var/cache/apt \
+    apt-get update
+RUN --mount=type=cache,target=/var/cache/apt \
+    apt-get install -y postgresql
+RUN ln -sf /usr/lib/postgresql/15/bin/initdb /usr/bin/initdb
 
 RUN python -m pip install --upgrade pip
 RUN python -m pip install --upgrade tox twine
+
+RUN useradd -m tests
+
+USER tests
diff --git a/docker/pycommon/Dockerfile b/docker/pycommon/Dockerfile
index ceea7e721fc3acaa124184e709986e04dbd9a3dd..4b7fe6d159a7c966aa556914795358f4f9012f88 100644
--- a/docker/pycommon/Dockerfile
+++ b/docker/pycommon/Dockerfile
@@ -1,6 +1,6 @@
 ARG BUILD_ENV=no_copy
 
-FROM python:3.11 AS build_no_copy
+FROM python:3.13 AS build_no_copy
 ADD ../../requirements.txt .
 COPY ../.. /work
 RUN rm -r /work/dist | true
@@ -8,11 +8,15 @@ RUN python -m pip install --user tox
 WORKDIR /work
 RUN python -m tox -e build
 
-FROM python:3.11 AS build_copy
+FROM python:3.13 AS build_copy
 COPY dist /work/dist
 
 FROM build_${BUILD_ENV} AS build
 
-FROM python:3.11-slim
+FROM python:3.13-slim
 COPY --from=build /work/dist /dist
+RUN --mount=type=cache,target=/var/cache/apt \
+    apt-get update
+RUN --mount=type=cache,target=/var/cache/apt \
+    apt-get install -y postgresql build-essential libpq-dev
 RUN python -m pip install /dist/*.whl
diff --git a/docs/cleanup.py b/docs/cleanup.py
deleted file mode 100644
index 3a4508d859234544bea35b1008e3c8e4f73d7cc0..0000000000000000000000000000000000000000
--- a/docs/cleanup.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python3
-
-#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
-#  SPDX-License-Identifier: Apache-2.0
-
-import os
-
-file_dir = os.path.dirname(os.path.realpath(__file__))
-
-clean_dir = os.path.join(file_dir, "source", "source_documentation")
-print(f"Cleaning.. {clean_dir}/*")
-
-if not os.path.exists(clean_dir):
-    exit()
-
-for file_name in os.listdir(clean_dir):
-    file = os.path.join(clean_dir, file_name)
-    
-    if file_name == "index.rst":
-        continue
-
-    print(f"Removing.. {file}")
-    os.remove(file)
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 3c6e46c6db7ddaf65e47cfa22c9ec0b914f7fd38..0000000000000000000000000000000000000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-sphinx!=1.6.6,!=1.6.7,>=1.6.5 # BSD
-sphinx-rtd-theme>=0.4.3 #MIT
-sphinxcontrib-apidoc>=0.3.0 #BSD
-myst-parser>=2.0 # MIT
-docutils>=0.17 # BSD
diff --git a/docs/source/conf.py b/docs/source/conf.py
deleted file mode 100644
index a0e3d3370e1b739aa7674220c0309bb2642a9b35..0000000000000000000000000000000000000000
--- a/docs/source/conf.py
+++ /dev/null
@@ -1,93 +0,0 @@
-#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
-#  SPDX-License-Identifier: Apache-2.0
-
-import os
-
-from pycommon import __version__
-
-# -- General configuration ----------------------------------------------------
-
-# Add any Sphinx extension module names here, as strings. They can be
-# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = [
-    "sphinx.ext.autodoc",
-    "sphinx.ext.viewcode",
-    "sphinxcontrib.apidoc",
-    "sphinx_rtd_theme",
-    "myst_parser"
-]
-
-# Assumes tox is used to call sphinx-build
-project_root_directory = os.getcwd()
-
-apidoc_module_dir = "../../pycommon"
-apidoc_output_dir = "source_documentation"
-apidoc_excluded_paths = []
-apidoc_separate_modules = True
-apidoc_toc_file = False
-# This should include private methods but does not work
-# https://github.com/sphinx-contrib/apidoc/issues/14
-apidoc_extra_args = ["--private"]
-
-# The suffix of source filenames.
-source_suffix = [".rst"]
-
-# The master toctree document.
-master_doc = "index"
-
-# General information about the project.
-project = "PyCommon"
-copyright = "2023, ASTRON"
-
-# openstackdocstheme options
-repository_name = "git.astron.nl/tmss/libraries/pycommon"
-bug_project = "none"
-bug_tag = ""
-html_last_updated_fmt = "%Y-%m-%d %H:%M"
-
-# If true, '()' will be appended to :func: etc. cross-reference text.
-add_function_parentheses = True
-
-version = __version__
-
-modindex_common_prefix = ["pycommon."]
-
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
-add_module_names = True
-
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = "sphinx"
-
-# -- Options for HTML output --------------------------------------------------
-
-# The theme to use for HTML and HTML Help pages.  Major themes that come with
-# Sphinx are currently 'default' and 'sphinxdoc'.
-# html_theme_path = ["."]
-html_theme = "sphinx_rtd_theme"
-html_static_path = ["static"]
-html_css_files = [
-    "css/custom.css",
-]
-
-# Output file base name for HTML help builder.
-htmlhelp_basename = "%sdoc" % project
-
-# Conf.py variables exported to sphinx rst files access using |NAME|
-variables_to_export = [
-    "project",
-    "copyright",
-    "version",
-]
-
-# Write to rst_epilog to export `variables_to_export` extract using `locals()`
-# https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-rst_epilog
-frozen_locals = dict(locals())
-rst_epilog = "\n".join(
-    map(
-        lambda x: f".. |{x}| replace:: {frozen_locals[x]}",  # noqa: F821
-        variables_to_export,
-    )
-)
-# Pep is not able to determine that frozen_locals always exists so noqa
-del frozen_locals
diff --git a/docs/source/index.rst b/docs/source/index.rst
deleted file mode 100644
index a1d9e8713a5ff26c1adcf58b4fa47d0f92fdfdca..0000000000000000000000000000000000000000
--- a/docs/source/index.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-====================================================
-Welcome to the documentation of PyCommon
-====================================================
-
-..
-    To define more variables see rst_epilog generation in conf.py
-
-Documentation for version: |version|
-
-Contents:
-
-.. toctree::
-   :maxdepth: 2
-
-   readme
-   source_documentation/index
diff --git a/docs/source/readme.rst b/docs/source/readme.rst
deleted file mode 100644
index 87c96deef60fc04b35a8b9b9cca2f72b7e64e70c..0000000000000000000000000000000000000000
--- a/docs/source/readme.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-.. include:: ../../README.md
-   :parser: myst_parser.sphinx_
diff --git a/docs/source/static/css/custom.css b/docs/source/static/css/custom.css
deleted file mode 100644
index 3ea8a2fc0c9c1ecb2b318cbc32edf90d2940c38d..0000000000000000000000000000000000000000
--- a/docs/source/static/css/custom.css
+++ /dev/null
@@ -1,14 +0,0 @@
-.orange { color: #c65d09; }
-
-.green { color: #5dc609; }
-
-.yellow { color: #c6c609; }
-
-.bolditalic {
-  font-weight: bold;
-  font-style: italic;
-}
-
-.rst-content code, .rst-content tt, code {
-  white-space: break-spaces;
-}
diff --git a/pycommon/CMakeLists.txt b/pycommon/CMakeLists.txt
deleted file mode 100644
index a68068607d44be3889aa0ee75c4ec356828f7178..0000000000000000000000000000000000000000
--- a/pycommon/CMakeLists.txt
+++ /dev/null
@@ -1,51 +0,0 @@
-# $Id: CMakeLists.txt 720 2014-12-08 16:29:33Z loose $
-
-lofar_package(PyCommon 1.0)
-
-lofar_find_package(Python 3.4 REQUIRED)
-include(PythonInstall)
-
-include(FindPythonModule)
-find_python_module(jsonschema)
-find_python_module(psycopg2)
-find_python_module(cx_Oracle)
-find_python_module(lxml)
-find_python_module(prometheus_client)
-
-set(_py_files
-  __init__.py
-  ssh_utils.py
-  cep4_utils.py
-  cobaltblocksize.py
-  threading_utils.py
-  lcu_utils.py
-  cache.py
-  dbcredentials.py
-  defaultmailaddresses.py
-  factory.py
-  math.py
-  methodtrigger.py
-  metrics.py
-  util.py
-  database.py
-  oracle.py
-  postgres.py
-  datetimeutils.py
-  flask_utils.py
-  h5_utils.py
-  subprocess_utils.py
-  xmlparse.py
-  json_utils.py
-  locking.py
-  test_utils.py
-  typing.py
-  toposort.py
-  ring_coordinates.py
-  station_coordinates.py
-  parameterset.py)
-
-python_install(${_py_files} DESTINATION lofar/common)
-
-lofar_add_bin_scripts(dbcredentials)
-
-add_subdirectory(test)
diff --git a/pycommon/__init__.py b/pycommon/__init__.py
index 0453289abdc17d088ea23afa2eeef7d2e43dd2d8..8fc7d9b8b360755f925c89d055e11404b8565c50 100644
--- a/pycommon/__init__.py
+++ b/pycommon/__init__.py
@@ -1,24 +1,5 @@
-# __init__.py: Module initialization file.
-#
-# Copyright (C) 2015
-# ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it
-# and/or modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be
-# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: __init__.py 1568 2015-09-18 15:21:11Z loose $
+#  Copyright (C) 2015 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 """
 Module initialization file.
diff --git a/pycommon/cache.py b/pycommon/cache.py
index 514bf53c24e2111237235af5554b8c9388f7eb8e..9f12a709930027b8ed5ea0c32e372259acd5f869 100644
--- a/pycommon/cache.py
+++ b/pycommon/cache.py
@@ -1,29 +1,11 @@
-# cache.py: function return value cache
-#
-# copyright (c) 2015
-# astron (netherlands institute for radio astronomy)
-# p.o.box 2, 7990 aa dwingeloo, the netherlands
-#
-# this file is part of the lofar software suite.
-# the lofar software suite is free software: you can redistribute it
-# and/or modify it under the terms of the gnu general public license as
-# published by the free software foundation, either version 3 of the
-# license, or (at your option) any later version.
-#
-# the lofar software suite is distributed in the hope that it will be
-# useful, but without any warranty; without even the implied warranty of
-# merchantability or fitness for a particular purpose.  see the
-# gnu general public license for more details.
-#
-# you should have received a copy of the gnu general public license along
-# with the lofar software suite. if not, see <http://www.gnu.org/licenses/>.
-#
-# $id: __init__.py 1568 2015-09-18 15:21:11z loose $
+#  Copyright (C) 2015 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 import functools
 
+
 class cache(object):
-    """ A simple cache for function call return values in Python 2. 
+    """ A simple cache for function call return values in Python 2.
 
         Use:
 
diff --git a/pycommon/cep4_utils.py b/pycommon/cep4_utils.py
index f2dfd20cd7a30534c9cc179fb8f718acf32e14b5..8caf0c9bfa55694c0e56ea6f39d799906731805f 100755
--- a/pycommon/cep4_utils.py
+++ b/pycommon/cep4_utils.py
@@ -1,30 +1,16 @@
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-from lofar.common.ssh_utils import ssh_cmd_list
-from subprocess import Popen, PIPE
-from lofar.common.subprocess_utils import check_output_returning_strings, communicate_returning_strings, execute_in_parallel
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
+import logging
 from random import randint
 import math
 import os
-from time import sleep
-from datetime import datetime, timedelta
 
-import logging
+from pycommon.ssh_utils import ssh_cmd_list
+from pycommon.subprocess_utils import (
+    check_output_returning_strings, execute_in_parallel
+)
+
 logger = logging.getLogger(__name__)
 
 # a selection of slurm states relevant for lofar usage
@@ -402,12 +388,3 @@ def parallelize_cmd_over_cep4_cpu_nodes(cmd, parallelizable_option, parallelizab
         logger.error('%s/%s parallelized cmds finished with errors', len(failed_results), num_workers)
 
     return success
-
-if __name__ == '__main__':
-    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
-    logger.info(convert_slurm_nodes_string_to_node_number_list('  \t  cpu[20-39,41,45-48]  '))
-    logger.info(convert_slurm_nodes_string_to_node_number_list('  \t  cpu03  '))
-    logger.info(get_cep4_available_nodes())
-    logger.info(get_cep4_available_nodes_sorted_ascending_by_load(min_nr_of_nodes=3, partition=SLURM_CPU_PARTITION))
-    logger.info(get_cep4_available_nodes_sorted_ascending_by_load(min_nr_of_nodes=3, partition=SLURM_GPU_PARTITION))
-
diff --git a/pycommon/cobaltblocksize.py b/pycommon/cobaltblocksize.py
index 20bf2a7e871c1fc2c207fad14634920b6b5de995..3ec0e89b9ccec002f7eab05e1e15de3bf6d8cbee 100755
--- a/pycommon/cobaltblocksize.py
+++ b/pycommon/cobaltblocksize.py
@@ -1,3 +1,6 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 """
   Code to derive the following parset input parameters for Cobalt. These keys need to be tuned
   specifically to make sure all Cobalt processing fits inside a block. Only two processing
@@ -26,7 +29,8 @@
 """
 
 from math import ceil
-from lofar.common.math import lcm
+from pycommon.math import lcm
+
 
 class CorrelatorSettings(object):
     """ Settings for the Correlator. """
diff --git a/pycommon/database.py b/pycommon/database.py
index eceb46a224cb9c0672cc6e863b74a524e0f16f3e..f8c8aa48978bfbbb35453123e66cccf5385958fb 100644
--- a/pycommon/database.py
+++ b/pycommon/database.py
@@ -1,23 +1,5 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 '''
 common abstract database connection class
@@ -25,11 +7,11 @@ common abstract database connection class
 
 import logging
 from datetime import  datetime, timedelta
-import collections
 import time
 import re
-from lofar.common.util import single_line_with_single_spaces
-from lofar.common.dbcredentials import DBCredentials
+
+from pycommon.util import single_line_with_single_spaces
+from pycommon.dbcredentials import DBCredentials
 
 logger = logging.getLogger(__name__)
 
@@ -37,6 +19,7 @@ FETCH_NONE=0
 FETCH_ONE=1
 FETCH_ALL=2
 
+
 class DatabaseError(Exception):
     pass
 
diff --git a/pycommon/datetimeutils.py b/pycommon/datetimeutils.py
index fb1df8788220bf9af22515b5903c63c8c6a1147f..73a29dfce13f4e65d1313f1b1588b7852ad93142 100644
--- a/pycommon/datetimeutils.py
+++ b/pycommon/datetimeutils.py
@@ -1,27 +1,7 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015    ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 from datetime import datetime, timedelta
-import sys
-import os
 
 
 def monthRanges(min_date, max_date, month_step=1):
diff --git a/pycommon/dbcredentials b/pycommon/dbcredentials
deleted file mode 100755
index df392039d7b16878f8b46a5655ee9e85b719f845..0000000000000000000000000000000000000000
--- a/pycommon/dbcredentials
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-# Just forward to the __main__ of the module
-exec python3 -m lofar.common.dbcredentials "$@"
diff --git a/pycommon/dbcredentials.py b/pycommon/dbcredentials.py
index 172e578451be8c221b5bc87c4d9ecc53a5d09ed0..3656e8e0bb2c5fdf5a2f2e5b03c4d49b9bd417f8 100644
--- a/pycommon/dbcredentials.py
+++ b/pycommon/dbcredentials.py
@@ -1,30 +1,12 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015    ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 from glob import glob
 import os
 import pwd
 from configparser import ConfigParser, NoSectionError, DuplicateSectionError
 from optparse import OptionGroup
-from os import stat, path, chmod
+from os import stat, chmod
 import logging
 
 logger = logging.getLogger(__name__)
@@ -323,51 +305,3 @@ def parse_options(options, filepatterns=None):
     if options.dbName:     creds.database = options.dbName
 
     return creds
-
-
-if __name__ == "__main__":
-    import sys
-    from optparse import OptionParser
-
-    parser = OptionParser("%prog [options]")
-    parser.add_option("-D", "--database", dest="database", type="string", default="",
-                      help="Print credentials of a specific database")
-    parser.add_option("-S", "--shell", dest="shell", action="store_true", default=False,
-                      help="Use machine-readable output for use in shell scripts")
-    parser.add_option("-L", "--list", dest="list", action="store_true", default=False,
-                      help="List known databases")
-    parser.add_option("-F", "--files", dest="files", action="store_true", default=False,
-                      help="List names of parsed configuration files")
-    (options, args) = parser.parse_args()
-
-    if not options.database and not options.list and not options.files:
-        logger.error("Missing database name")
-        parser.print_help()
-        sys.exit(1)
-
-    dbc = DBCredentials()
-
-    if options.files:
-        """ Print list of configuration files that we've read. """
-        if dbc.files:
-            logger.info("\n".join(dbc.files))
-        sys.exit(0)
-
-    if options.list:
-        """ Print list of databases. """
-        databases = dbc.list()
-        if databases:
-            logger.info("\n".join(databases))
-        sys.exit(0)
-
-    """ Print credentials of a specific database. """
-    creds = dbc.get(options.database)
-
-    if options.shell:
-        print("DBUSER=%s" % (creds.user,))
-        print("DBPASSWORD=%s" % (creds.password,))
-        print("DBDATABASE=%s" % (creds.database,))
-        print("DBHOST=%s" % (creds.host,))
-        print("DBPORT=%s" % (creds.port,))
-    else:
-        logger.info(str(creds))
diff --git a/pycommon/defaultmailaddresses.py b/pycommon/defaultmailaddresses.py
index f23e6f63ac75d9e1d1b7b9ff67a90daf61a0dfc1..8eb25f72759e6074383665f918e53c6a807c4334 100644
--- a/pycommon/defaultmailaddresses.py
+++ b/pycommon/defaultmailaddresses.py
@@ -1,25 +1,6 @@
-# defaultmailaddresses.py: default mail addresses for the LOFAR software
-#
-# Copyright (C) 2017
-# ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it
-# and/or modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be
-# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: $
-#
+#  Copyright (C) 2017 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 """
 This package contains the default mail addresses used in the LOFAR software
 """
diff --git a/tests/.gitkeep b/pycommon/entrypoints/__init__.py
similarity index 100%
rename from tests/.gitkeep
rename to pycommon/entrypoints/__init__.py
diff --git a/pycommon/entrypoints/cep4_info.py b/pycommon/entrypoints/cep4_info.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8c272d537a80a3aebb1dc2d3b5e8c37ea80b401
--- /dev/null
+++ b/pycommon/entrypoints/cep4_info.py
@@ -0,0 +1,21 @@
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
+import logging
+
+from cep4_utils import (
+    convert_slurm_nodes_string_to_node_number_list,
+    get_cep4_available_nodes, get_cep4_available_nodes_sorted_ascending_by_load,
+    SLURM_GPU_PARTITION, SLURM_CPU_PARTITION
+)
+
+logger = logging.getLogger(__name__)
+
+
+def main():
+    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
+    logger.info(convert_slurm_nodes_string_to_node_number_list('  \t  cpu[20-39,41,45-48]  '))
+    logger.info(convert_slurm_nodes_string_to_node_number_list('  \t  cpu03  '))
+    logger.info(get_cep4_available_nodes())
+    logger.info(get_cep4_available_nodes_sorted_ascending_by_load(min_nr_of_nodes=3, partition=SLURM_CPU_PARTITION))
+    logger.info(get_cep4_available_nodes_sorted_ascending_by_load(min_nr_of_nodes=3, partition=SLURM_GPU_PARTITION))
diff --git a/pycommon/entrypoints/cobaltblocksize.py b/pycommon/entrypoints/cobaltblocksize.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4168817df0b3d96be2845485c5c16540ef52981
--- /dev/null
+++ b/pycommon/entrypoints/cobaltblocksize.py
@@ -0,0 +1,53 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
+import argparse
+import sys
+
+from pycommon.cobaltblocksize import (
+    CorrelatorSettings, StokesSettings, BlockConstraints, BlockSize
+)
+
+
+def main():
+    parser = argparse.ArgumentParser(description='Compute COBALT block sizes, based on the given constraints.')
+    parser.add_argument('-c', '--correlate', dest='correlate', action='store_true', default=False, help='enable the correlator')
+    parser.add_argument('--correlator_nrchannels', type=int, default=64, help='Correlator.nrChannelsPerSubband (default: %(default)s)')
+    parser.add_argument('--correlator_integration_time', type=float, default=1.0, help='Correlator.integrationTime (default: %(default)s)')
+
+    parser.add_argument('--coherent',  dest='coherent',  action='store_true', default=False, help='enable coherent beamforming')
+    parser.add_argument('--incoherent',  dest='incoherent',  action='store_true', default=False, help='enable incoherent beamforming')
+
+    parser.add_argument('--coherent_nrchannels', type=int, default=16, help='CoherentStokes.nrChannelsPerSubband (default: %(default)s)')
+    parser.add_argument('--coherent_time_integration_factor', type=int, default=1, help='CoherentStokes.timeIntegrationFactor (default: %(default)s)')
+
+    parser.add_argument('--incoherent_nrchannels', type=int, default=16, help='IncoherentStokes.nrChannelsPerSubband (default: %(default)s)')
+    parser.add_argument('--incoherent_time_integration_factor', type=int, default=1, help='IncoherentStokes.timeIntegrationFactor (default: %(default)s)')
+
+    args = parser.parse_args()
+
+    if not args.correlate and not args.coherent and not args.incoherent:
+        parser.print_help()
+        sys.exit(1)
+
+    corr = CorrelatorSettings()
+    corr.nrChannelsPerSubband = args.correlator_nrchannels
+    corr.integrationTime      = args.correlator_integration_time
+
+    coh = StokesSettings()
+    coh.nrChannelsPerSubband  = args.coherent_nrchannels
+    coh.timeIntegrationFactor = args.coherent_time_integration_factor
+
+    incoh = StokesSettings()
+    incoh.nrChannelsPerSubband  = args.incoherent_nrchannels
+    incoh.timeIntegrationFactor = args.incoherent_time_integration_factor
+
+    constraints = BlockConstraints(correlatorSettings=corr if args.correlate else None,
+                                   coherentStokesSettings=[coh] if args.coherent else [],
+                                   incoherentStokesSettings=[incoh] if args.incoherent else [])
+
+    # will throw if the blocksize cannot be computed
+    blocksize = BlockSize(constraints)
+
+    # print optimal block size
+    print(blocksize.blockSize)
diff --git a/pycommon/entrypoints/dbcredentials.py b/pycommon/entrypoints/dbcredentials.py
new file mode 100644
index 0000000000000000000000000000000000000000..20d0d0aa850d1570409e9c2c2023a788a229ed7a
--- /dev/null
+++ b/pycommon/entrypoints/dbcredentials.py
@@ -0,0 +1,63 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
+import sys
+from optparse import OptionParser
+import logging
+
+from pycommon.dbcredentials import DBCredentials
+
+logger = logging.getLogger()
+
+
+def main():
+    parser = OptionParser("%prog [options]")
+    parser.add_option(
+            "-D", "--database", dest="database", type="string", default="",
+            help="Print credentials of a specific database"
+    )
+    parser.add_option(
+            "-S", "--shell", dest="shell", action="store_true", default=False,
+            help="Use machine-readable output for use in shell scripts"
+    )
+    parser.add_option(
+            "-L", "--list", dest="list", action="store_true", default=False,
+            help="List known databases"
+    )
+    parser.add_option(
+            "-F", "--files", dest="files", action="store_true", default=False,
+            help="List names of parsed configuration files"
+    )
+    (options, args) = parser.parse_args()
+
+    if not options.database and not options.list and not options.files:
+        logger.error("Missing database name")
+        parser.print_help()
+        sys.exit(1)
+
+    dbc = DBCredentials()
+
+    if options.files:
+        """ Print list of configuration files that we've read. """
+        if dbc.files:
+            logger.info("\n".join(dbc.files))
+        sys.exit(0)
+
+    if options.list:
+        """ Print list of databases. """
+        databases = dbc.list()
+        if databases:
+            logger.info("\n".join(databases))
+        sys.exit(0)
+
+    """ Print credentials of a specific database. """
+    creds = dbc.get(options.database)
+
+    if options.shell:
+        print("DBUSER=%s" % (creds.user,))
+        print("DBPASSWORD=%s" % (creds.password,))
+        print("DBDATABASE=%s" % (creds.database,))
+        print("DBHOST=%s" % (creds.host,))
+        print("DBPORT=%s" % (creds.port,))
+    else:
+        logger.info(str(creds))
diff --git a/pycommon/entrypoints/oracle.py b/pycommon/entrypoints/oracle.py
new file mode 100644
index 0000000000000000000000000000000000000000..15525fe2fe3aa23689c39d9b38e5f1b20b0270da
--- /dev/null
+++ b/pycommon/entrypoints/oracle.py
@@ -0,0 +1,21 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
+import logging
+
+from pycommon.database import FETCH_ALL
+from pycommon.dbcredentials import DBCredentials
+from pycommon.oracle import OracleDatabaseConnection
+
+
+def main():
+    logging.basicConfig(format = '%(asctime)s %(levelname)s %(message)s', level = logging.INFO)
+
+    dbcreds = DBCredentials().get('LTA')
+    print(dbcreds.stringWithHiddenPassword())
+
+    with OracleDatabaseConnection(dbcreds=dbcreds) as db:
+        from pprint import pprint
+        pprint(db.executeQuery("SELECT table_name, owner, tablespace_name FROM all_tables", fetch=FETCH_ALL))
+        # pprint(db.executeQuery("SELECT * FROM awoper.aweprojects", fetch=FETCH_ALL))
+        #pprint(db.executeQuery("SELECT * FROM awoper.aweprojectusers", fetch=FETCH_ALL))
diff --git a/pycommon/factory.py b/pycommon/factory.py
index a80c6137e0aad1042ef22ab3aa149d2135452003..448789f05b7a316041f92ec59468e34b79fdd222 100644
--- a/pycommon/factory.py
+++ b/pycommon/factory.py
@@ -1,24 +1,5 @@
-# factory.py: Generic object factory.
-#
-# Copyright (C) 2015
-# ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it
-# and/or modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be
-# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: factory.py 1584 2015-10-02 12:10:14Z loose $
+#  Copyright (C) 2015 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 """
 Module for a generic object factory.
diff --git a/pycommon/flask_utils.py b/pycommon/flask_utils.py
index c254c6a794361a026a10936831b54554db6af626..f31a0d6102b9474be54a60aa430754f21b92002b 100644
--- a/pycommon/flask_utils.py
+++ b/pycommon/flask_utils.py
@@ -1,29 +1,10 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 from flask import after_this_request, request
 from io import BytesIO as IO
 import gzip
 import functools
-from datetime import datetime
 
 def gzipped(f):
     @functools.wraps(f)
diff --git a/pycommon/h5_utils.py b/pycommon/h5_utils.py
index 7baa7cdcc156c56f5ae6efd59613c78581895a57..62d7daf0b9f034619a9dd2d45cdcfa01be55a284 100644
--- a/pycommon/h5_utils.py
+++ b/pycommon/h5_utils.py
@@ -1,33 +1,21 @@
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
+import logging
 import os.path
 from datetime import datetime, timedelta
 from time import sleep
 import errno
 import os
 
-# prevent annoying h5py future/deprecation warnings
-os.environ["TF_CPP_MIN_LOG_LEVEL"]="3"
 import h5py
 
-import logging
+# # prevent annoying h5py future/deprecation warnings
+# os.environ["TF_CPP_MIN_LOG_LEVEL"]="3"
+
 logger = logging.getLogger(__name__)
 
+
 class SharedH5File():
     """
     Wrapper class aroung h5py.File to open an hdf5 file in read, write, or read/write mode safely,
diff --git a/pycommon/json_utils.py b/pycommon/json_utils.py
index 9e0d4b53dfa5f6dfe5ef82a81620b821f541e1f4..38a2849dae8731475a5c5f2341fddb3109f374c3 100644
--- a/pycommon/json_utils.py
+++ b/pycommon/json_utils.py
@@ -1,30 +1,18 @@
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
+from copy import deepcopy
+from datetime import datetime, timedelta
 import json
 import logging
 import time
 import typing
 
 import jsonschema
-from copy import deepcopy
 import requests
-from datetime import datetime, timedelta
-from .util import single_line_with_single_spaces
+
+from pycommon.util import single_line_with_single_spaces
+
 
 class JSONError(Exception):
     pass
diff --git a/pycommon/lcu_utils.py b/pycommon/lcu_utils.py
index c0a17b1e811a91f0a176eb2a21d8c804c6662564..9c15b055bc9f685ae7197ef58581fa9b84338a50 100755
--- a/pycommon/lcu_utils.py
+++ b/pycommon/lcu_utils.py
@@ -1,32 +1,20 @@
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-from lofar.common.ssh_utils import ssh_cmd_list
-from lofar.common.subprocess_utils import execute_in_parallel, wrap_composite_command, communicate_returning_strings
-from subprocess import Popen, PIPE
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
+import logging
+from subprocess import Popen, PIPE
 import os
 import uuid
 import struct
-from datetime import datetime, timedelta
 
-import logging
+from pycommon.ssh_utils import ssh_cmd_list
+from pycommon.subprocess_utils import (
+    execute_in_parallel, wrap_composite_command, communicate_returning_strings
+)
+
 logger = logging.getLogger(__name__)
 
+
 class LCURuntimeError(RuntimeError):
     pass
 
@@ -449,14 +437,3 @@ def parse_station_calibration_file(filename):
 
         # return tuple of header and complex table in result dict
         return (header_dict, complexdata)
-
-
-
-if __name__ == '__main__':
-    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG)
-    import pprint
-    # pprint.pprint(get_station_cable_delays(['CS004', 'CS005']))
-    # print get_station_calibration_tables(['CS001', 'RS407'], antenna_set_and_filter='LBA_INNER-10_90') #['CS001', 'DE601'])
-    #pprint.pprint(execute_in_parallel_over_stations(cmd=wrap_composite_command('sleep 1; date'),
-    #                                                stations=['cs026c' for i in range(10)]))
-    pprint.pprint(execute_in_parallel_over_station_group(cmd=wrap_composite_command('sleep 1 ; date ; sleep 1 ;'), station_group='today_core'))
diff --git a/pycommon/locking.py b/pycommon/locking.py
index a8ff6d710ef543f3a91b5b489ffbe6a125ee5f96..4fef843bee203c554b7d7b1f9040951b353d7f3e 100644
--- a/pycommon/locking.py
+++ b/pycommon/locking.py
@@ -8,7 +8,6 @@
     NamedAtomicLock - A Named atomic lock local to the machine
 
 '''
-# vim: set ts=4 sw=4 expandtab :
 
 
 import os
@@ -31,6 +30,7 @@ try:
 except:
     FileNotFoundError = OSError
 
+
 class NamedAtomicLock(object):
 
     def __init__(self, name, lockDir=None, maxLockAge=None):
@@ -241,7 +241,7 @@ class NamedAtomicLock(object):
         # If we don't hold it currently, return False
         if self.held is False:
             return False
-        
+
         # Otherwise if we think we hold it, but it is not held, we have lost it.
         if not self.isHeld:
             self.acquiredAt = None
diff --git a/pycommon/math.py b/pycommon/math.py
index 3dd0b351b9ab6a84ed89cf3581be91aa4b0740e2..8558d43a1bc10ba2372751350b8ba42944b29654 100644
--- a/pycommon/math.py
+++ b/pycommon/math.py
@@ -1,3 +1,6 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 try:
     from math import gcd
 except ImportError:
diff --git a/pycommon/methodtrigger.py b/pycommon/methodtrigger.py
index 338d1506687444209f45068b68376f6ed444d60d..09cfd93a7d871bf76008534c12b696a9e1da1696 100644
--- a/pycommon/methodtrigger.py
+++ b/pycommon/methodtrigger.py
@@ -1,11 +1,15 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 from threading import Lock, Condition
 
 __all__ = ["MethodTrigger"]
 
+
 class MethodTrigger:
   """
     Set a flag when a specific method is called, possibly asynchronously. Caller can wait on this flag.
-    
+
     Example:
 
       class Foo(object):
diff --git a/pycommon/metrics.py b/pycommon/metrics.py
index d3498ce563677a34957cbb11aae55ce90cb68230..b11e65a77bb980e63015d340b851f5cb3a411ccf 100644
--- a/pycommon/metrics.py
+++ b/pycommon/metrics.py
@@ -1,19 +1,5 @@
-# Copyright (C) 2024  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 from functools import wraps
 import time
@@ -24,9 +10,10 @@ from prometheus_client.utils import INF
 # Buckets that are more useful for tracking longer durations
 LONG_DURATION_BUCKETS = (0.1, 0.2, 0.5, 1.0, 10.0, 30.0, 60.0, 120.0, 300.0, 600.0, 1800.0, 3600.0, INF)
 
+
 def _add_default_metrics():
     """Create generic metrics that we expose by default."""
-  
+
     uptime = Gauge("uptime", "Uptime of the service")
     start_time = time.time()
     uptime.set_function(lambda: time.time() - start_time)
diff --git a/pycommon/oracle.py b/pycommon/oracle.py
index de63802072b0cea65a1a1870b3873d3edf2ddeee..0fe83964f5d6cec2a151b9f74b19f154132c44f6 100644
--- a/pycommon/oracle.py
+++ b/pycommon/oracle.py
@@ -1,35 +1,21 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 '''
 Module with nice postgres helper methods and classes.
 '''
 
 import logging
-from datetime import datetime, timedelta
+from datetime import datetime
 import cx_Oracle
 import re
-from lofar.common.dbcredentials import DBCredentials
-from lofar.common.database import AbstractDatabaseConnection, DatabaseError, DatabaseConnectionError, DatabaseExecutionError, FETCH_NONE, FETCH_ONE, FETCH_ALL
-from lofar.common.util import single_line_with_single_spaces
+
+from pycommon.dbcredentials import DBCredentials
+from pycommon.database import (
+    AbstractDatabaseConnection, DatabaseError, DatabaseConnectionError,
+    DatabaseExecutionError, FETCH_NONE, FETCH_ONE, FETCH_ALL
+)
+from pycommon.util import single_line_with_single_spaces
 
 logger = logging.getLogger(__name__)
 
@@ -134,16 +120,3 @@ class OracleDatabaseConnection(AbstractDatabaseConnection):
         else:
             # wrap original error in OracleDBQueryExecutionError
             raise OracleDBQueryExecutionError("Could not execute query '%s' error=%s" % (query_log_line, error_string))
-
-
-if __name__ == '__main__':
-    logging.basicConfig(format = '%(asctime)s %(levelname)s %(message)s', level = logging.INFO)
-
-    dbcreds = DBCredentials().get('LTA')
-    print(dbcreds.stringWithHiddenPassword())
-
-    with OracleDatabaseConnection(dbcreds=dbcreds) as db:
-        from pprint import pprint
-        pprint(db.executeQuery("SELECT table_name, owner, tablespace_name FROM all_tables", fetch=FETCH_ALL))
-        # pprint(db.executeQuery("SELECT * FROM awoper.aweprojects", fetch=FETCH_ALL))
-        #pprint(db.executeQuery("SELECT * FROM awoper.aweprojectusers", fetch=FETCH_ALL))
diff --git a/pycommon/parameterset.py b/pycommon/parameterset.py
index b8ddb816c5f5163e1f99cd3c41cc7f4a485b54fd..8805eb0d81a7297665f60917dd29ae6f3b5ef996 100644
--- a/pycommon/parameterset.py
+++ b/pycommon/parameterset.py
@@ -1,23 +1,5 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015    ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 """Drop-in replacement for LCS/pyparameterset, which in turn wraps LCS/Common/src/ParameterSet.cc.
 
diff --git a/pycommon/postgres.py b/pycommon/postgres.py
index 4e5b2a02b7429377968ba0b24de67a877182a9da..15c64a86d489e5eb2b94f01c22d4f9e0f2b88d48 100644
--- a/pycommon/postgres.py
+++ b/pycommon/postgres.py
@@ -1,23 +1,5 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 '''
 Module with nice postgres helper methods and classes.
@@ -26,19 +8,22 @@ Module with nice postgres helper methods and classes.
 import logging
 from threading import Thread, Lock
 from queue import Queue, Empty
-from datetime import  datetime, timedelta
+from datetime import  datetime
 import collections
-import time
-from typing import Union
 import re
 import select
+
 import psycopg2
 import psycopg2.extras
 import psycopg2.extensions
-from lofar.common.util import single_line_with_single_spaces
-from lofar.common.datetimeutils import totalSeconds
-from lofar.common.dbcredentials import DBCredentials
-from lofar.common.database import AbstractDatabaseConnection, DatabaseError, DatabaseConnectionError, DatabaseExecutionError, FETCH_NONE, FETCH_ONE, FETCH_ALL
+
+from pycommon.util import single_line_with_single_spaces
+from pycommon.datetimeutils import totalSeconds
+from pycommon.dbcredentials import DBCredentials
+from pycommon.database import (
+    AbstractDatabaseConnection, DatabaseError, DatabaseConnectionError,
+    DatabaseExecutionError, FETCH_NONE, FETCH_ONE, FETCH_ALL
+)
 
 logger = logging.getLogger(__name__)
 
diff --git a/pycommon/ring_coordinates.py b/pycommon/ring_coordinates.py
index 454dddb9d22ea7ce626dc1ba0a32a574ddd755b3..986786e40fdc93444acc6991f98976531cc712a9 100755
--- a/pycommon/ring_coordinates.py
+++ b/pycommon/ring_coordinates.py
@@ -1,9 +1,7 @@
-#!/usr/bin/env python3
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
-import sys
 from math import sqrt, cos, sin, pi, asin, atan2
-import subprocess
-import itertools
 
 
 class RingCoordinates:
diff --git a/pycommon/ssh_utils.py b/pycommon/ssh_utils.py
index 4d44a6306cd0574093b54f021dc6442de4459b9a..1cec3a719a1a942434849e8f94903fa08264a2e5 100644
--- a/pycommon/ssh_utils.py
+++ b/pycommon/ssh_utils.py
@@ -1,23 +1,11 @@
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 import logging
+
 logger = logging.getLogger(__name__)
 
+
 def ssh_cmd_list(host, user='lofarsys', disable_strict_host_key_checking: bool=False, disable_remote_pseudo_terminal: bool=False):
     '''
     returns a subprocess compliant command list to do an ssh call to the given node
diff --git a/pycommon/station_coordinates.py b/pycommon/station_coordinates.py
index a9b8bb9b1965870e09405b2bf6a331ae728221ed..0ea0b6d2dbb840df9e27b51306153dc4fca0aa7d 100644
--- a/pycommon/station_coordinates.py
+++ b/pycommon/station_coordinates.py
@@ -1,3 +1,6 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 from ast import literal_eval
 import os
 import functools
diff --git a/pycommon/subprocess_utils.py b/pycommon/subprocess_utils.py
index 26d6f2adca4f6c49bebba002a334808f51b8b8a3..cd28ce3d04f43513fb5ee4301cc5ec005540ceaf 100644
--- a/pycommon/subprocess_utils.py
+++ b/pycommon/subprocess_utils.py
@@ -1,3 +1,6 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 import logging
 from datetime import datetime, timedelta
 from time import sleep
@@ -11,6 +14,7 @@ except ImportError:
 
 logger = logging.getLogger(__name__)
 
+
 class SubprocessTimoutError(TimeoutError):
     '''an error class indication that the running subprocess to longer than expected to complete'''
     pass
diff --git a/pycommon/test_utils.py b/pycommon/test_utils.py
index 408b4b8d78a424b74326f54b97fab067bcf5e111..cf07128471efd66754d6c6b85ee1af15d9a51e29 100644
--- a/pycommon/test_utils.py
+++ b/pycommon/test_utils.py
@@ -1,25 +1,6 @@
-# test_utils.py: test utils for lofar software
-#
-# Copyright (C) 2015
-# ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it
-# and/or modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be
-# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: test_utils.py 1584 2015-10-02 12:10:14Z loose $
-#
+#  Copyright (C) 2015 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 """
 This package contains different utilities that are common for LOFAR software testing
 """
diff --git a/pycommon/threading_utils.py b/pycommon/threading_utils.py
index aeccb4f00512f06f4af42201ee3dddb20ccdedeb..edb2fe27ad15898cf0a20aab9bafecd57a5a5c07 100644
--- a/pycommon/threading_utils.py
+++ b/pycommon/threading_utils.py
@@ -1,25 +1,6 @@
-# threading_utils.py: test utils for lofar software
-#
-# Copyright (C) 2015
-# ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it
-# and/or modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be
-# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id$
-#
+#  Copyright (C) 2015 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 """
 This module contains various utilities/methods that are common for LOFAR threading usage
 """
@@ -30,6 +11,7 @@ import logging
 
 logger = logging.getLogger(__name__)
 
+
 class TimeoutLock:
     """
     A TimeoutLock is a threading.RLock which you can use in a 'with' context in conjunction with a timeout.
diff --git a/pycommon/toposort.py b/pycommon/toposort.py
index 1c07a422b07c00a01e2c08bcb36e21611c62ef99..dab68e6a9b0af15c41276c017ca4359fb24f99ce 100644
--- a/pycommon/toposort.py
+++ b/pycommon/toposort.py
@@ -1,22 +1,5 @@
-# toposort.py: Topological Sort, to sort nodes of DAGs
-#
-# copyright (c) 2021
-# astron (netherlands institute for radio astronomy)
-# p.o.box 2, 7990 aa dwingeloo, the netherlands
-#
-# this file is part of the lofar software suite.
-# the lofar software suite is free software: you can redistribute it
-# and/or modify it under the terms of the gnu general public license as
-# published by the free software foundation, either version 3 of the
-# license, or (at your option) any later version.
-#
-# the lofar software suite is distributed in the hope that it will be
-# useful, but without any warranty; without even the implied warranty of
-# merchantability or fitness for a particular purpose.  see the
-# gnu general public license for more details.
-#
-# you should have received a copy of the gnu general public license along
-# with the lofar software suite. if not, see <http://www.gnu.org/licenses/>.
+#  Copyright (C) 2021 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 class Graph:
     """ Graph definition to use in the topological sort. """
@@ -68,11 +51,11 @@ class Graph:
 def toposorted(graph: Graph) -> list:
     """ Return the list of vertices, where, for each pair of vertices (a,b),
         a appears earlier in the list than b if there is a path a -> b.
-        
+
         Regular sorting routines (like list.sort) fundamentally cannot do this,
         as they demand a total ordering, that is, the comparison function
         must yield the correct result for every pair of elements (a,b).
-        
+
         NOTE: Since a graph provides only a partial ordering, the order of
         the output list can depend on the order of the vertices and edges in
         the graph. For many graphs multiple solutions are valid, after all. """
diff --git a/pycommon/typing.py b/pycommon/typing.py
index cd154ec09a2352afe744e5605a460a989b6413bc..040fe3b2f71bb5e45d8fa912aab254a6fe71cfbd 100644
--- a/pycommon/typing.py
+++ b/pycommon/typing.py
@@ -1,23 +1,10 @@
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 from functools import wraps
 import inspect
 
+
 def check_type_hints(func):
     """ Decorator that verifies the type hints of the decorated function.
 
diff --git a/pycommon/util.py b/pycommon/util.py
index b0f55aec637bcfc8188f7c45a4f09bab7b8ae9cb..7af3cc95dabe357636461810bfa89cda57533f6e 100644
--- a/pycommon/util.py
+++ b/pycommon/util.py
@@ -1,25 +1,6 @@
-# util.py: utils for lofar software
-#
-# Copyright (C) 2015
-# ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O.Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it
-# and/or modify it under the terms of the GNU General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be
-# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: util.py 1584 2015-10-02 12:10:14Z loose $
-#
+#  Copyright (C) 2015 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 """
 This package contains different utilities that are common for LOFAR software
 """
@@ -29,6 +10,7 @@ import os, os.path
 import time
 from copy import deepcopy
 import logging
+
 logger = logging.getLogger(__name__)
 
 def check_bit(value, bit):
diff --git a/pycommon/xmlparse.py b/pycommon/xmlparse.py
index eef946642952b92a3a18146d2ac84be6875ee351..1e53403acf927d8e22c6a8c9cf3740f061e7dc7e 100644
--- a/pycommon/xmlparse.py
+++ b/pycommon/xmlparse.py
@@ -1,7 +1,11 @@
-from lxml import etree
-from io import BytesIO
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 from typing import Union
+from io import BytesIO
+
+from lxml import etree
+
 
 def parse_xml_string_or_bytestring(xml_content: Union[str, bytes], parser=None) -> etree:
     """
@@ -14,4 +18,4 @@ def parse_xml_string_or_bytestring(xml_content: Union[str, bytes], parser=None)
     else:
         bstr = xml_content
 
-    return etree.parse(BytesIO(bstr), parser=parser)
\ No newline at end of file
+    return etree.parse(BytesIO(bstr), parser=parser)
diff --git a/requirements.txt b/requirements.txt
index 8f81bc285d0db9ea0945151fed0cd47eec4ee6a2..fd7bffb8cfeba9c66abc45560de358fcdc7544ad 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,8 @@
-importlib-metadata>=0.12, <5.0;python_version<"3.8"
-numpy
+psycopg2
+lxml
+jsonschema
+requests
+flask
+h5py
+prometheus_client
+cx_Oracle
diff --git a/setup.cfg b/setup.cfg
index 513b55d205df122a779ab7f0968b651d29c8798d..f4d1501248f4a149f9120a1388b4967d458ef4e6 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,9 +1,9 @@
 [metadata]
 name = pycommon
-description = An example package for CI/CD working group
+description = PyCommon package for TMSS backend
 long_description = file: README.md
 long_description_content_type = text/markdown
-url = https://git.astron.nl/templates/python-package
+url = https://git.astron.nl/tmss/libraries/pycommon
 license = GNU General Public License v3.0
 classifiers =
     Development Status :: 5 - Production/Stable
@@ -15,20 +15,26 @@ classifiers =
     Programming Language :: Python
     Programming Language :: Python :: 3
     Programming Language :: Python :: 3 :: Only
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
     Programming Language :: Python :: 3.12
+    Programming Language :: Python :: 3.13
     Topic :: Scientific/Engineering
     Topic :: Scientific/Engineering :: Astronomy
 
 [options]
 include_package_data = true
 packages = find:
-python_requires = >=3.8
+python_requires = >=3.10
 install_requires = file: requirements.txt
 
+[options.entry_points]
+console_scripts =
+    cep4-info = pycommon.entrypoints.cep4_info:main
+    cobaltblocksize = pycommon.entrypoints.cobaltblocksize:main
+    dbcredentials = pycommon.entrypoints.dbcredentials:main
+    oracle = pycommon.entrypoints.oracle:main
+
 [flake8]
 max-line-length = 88
 extend-ignore = E203
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
deleted file mode 100644
index fec40b34f4ef6e8122e44738463851fe32ec0eb5..0000000000000000000000000000000000000000
--- a/tests/CMakeLists.txt
+++ /dev/null
@@ -1,45 +0,0 @@
-# $Id$
-
-IF(BUILD_TESTING)
-    lofar_find_package(Python 3.4 REQUIRED)
-
-    include(PythonInstall)
-
-    set(_py_files
-      __init__.py
-      postgres.py
-      dbcredentials.py)
-
-    python_install(${_py_files} DESTINATION lofar/common/testing)
-
-    include(FindPythonModule)
-    find_python_module(testing.postgresql)
-
-    include(LofarCTest)
-
-    configure_file(
-      "${CMAKE_CURRENT_SOURCE_DIR}/python-coverage.sh.in"
-      "${CMAKE_BINARY_DIR}/bin/python-coverage.sh"  # internal, no need to install
-    )
-
-    lofar_add_test(t_cache)
-    lofar_add_test(t_cobaltblocksize)
-    lofar_add_test(t_dbcredentials)
-    lofar_add_test(t_defaultmailaddresses)
-    lofar_add_test(t_methodtrigger)
-    lofar_add_test(t_parameterset)
-    lofar_add_test(t_util)
-    lofar_add_test(t_test_utils)
-    lofar_add_test(t_cep4_utils)
-    lofar_add_test(t_toposort)
-    lofar_add_test(t_typing)
-
-    IF(PYTHON_JSONSCHEMA)
-        lofar_add_test(t_json_utils)
-    ENDIF()
-
-    IF(PYTHON_PSYCOPG2 AND PYTHON_TESTING.POSTGRESQL)
-        lofar_add_test(t_postgres)
-    ENDIF()
-
-ENDIF()
diff --git a/tests/__init__.py b/tests/__init__.py
index 05ca2190604b552a4bf261ddae3f0bebcc69cc4f..7a842a45cf5e6a7d9210bf2bacb727950e50ff8c 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,5 +1,5 @@
-from lofar.common import isProductionEnvironment
+from pycommon import isProductionEnvironment
 
 if isProductionEnvironment():
     print("The test modules in %s should not be imported and used in a lofar production environment" % __package__)
-    exit(1)
\ No newline at end of file
+    exit(1)
diff --git a/tests/dbcredentials.py b/tests/dbcredentials.py
index c2279f10851299d9b06d87a36011320386ccfe45..f3ba4610a75201706ff84ff7ccaad21688fa3196 100755
--- a/tests/dbcredentials.py
+++ b/tests/dbcredentials.py
@@ -1,31 +1,14 @@
-#!/usr/bin/env python3
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
-# Copyright (C) 2012-2015    ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
-import os, sys
+import os
 import uuid
-
 import logging
 
+from pycommon.dbcredentials import Credentials, DBCredentials
+
 logger = logging.getLogger(__name__)
 
-from lofar.common.dbcredentials import Credentials, DBCredentials
 
 class TemporaryCredentials():
     ''' A helper class which creates/destroys dbcredentials automatically.
@@ -98,4 +81,4 @@ class TemporaryCredentials():
         except Exception as e:
             logger.error("Could not remove temporary credentials file '%s': %s", self._dbcreds_path, e)
 
-__all__ = ['TemporaryCredentials']
\ No newline at end of file
+__all__ = ['TemporaryCredentials']
diff --git a/tests/postgres.py b/tests/postgres.py
index 627c1de6c2c3247afc95ac2099aad29e07f16897..25891739da088c1fa567d9183299cba73bdb057d 100755
--- a/tests/postgres.py
+++ b/tests/postgres.py
@@ -1,38 +1,22 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015    ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# $Id$
-import psycopg2
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
+from datetime import datetime, timedelta
 import os, sys
 import logging
-import uuid
-
-logger = logging.getLogger(__name__)
 
+import psycopg2
 import testing.postgresql
-from lofar.common.dbcredentials import Credentials
-from lofar.common.postgres import PostgresDatabaseConnection
-from lofar.common.testing.dbcredentials import TemporaryCredentials
-from lofar.common.util import find_free_port
-from datetime import datetime, timedelta
 
-from lofar.common.locking import NamedAtomicLock
+from pycommon.dbcredentials import Credentials
+from pycommon.postgres import PostgresDatabaseConnection
+from pycommon.util import find_free_port
+from pycommon.locking import NamedAtomicLock
+
+from tests.dbcredentials import TemporaryCredentials
+
+logger = logging.getLogger(__name__)
+
 
 class PostgresTestDatabaseInstance():
     ''' A helper class which instantiates a running postgres server (not interfering with any other test/production postgres servers)
diff --git a/tests/python-coverage.sh.in b/tests/python-coverage.sh.in
deleted file mode 100755
index 3c5e166572ec8e5b220b398cc6ac1bf0c1bea52d..0000000000000000000000000000000000000000
--- a/tests/python-coverage.sh.in
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/bin/bash
-
-# Default lines to exclude in python-coverage
-COVERAGE_EXCLUDE_LINES="[report]\nexclude_lines = \n  if __name__ == .__main__.\n  def main\n"
-
-# Determine python-coverage executable
-if type "coverage3" >& /dev/null; then
-  COVERAGE=coverage3
-elif type "python3-coverage" >& /dev/null; then
-  COVERAGE=python3-coverage
-else
-  COVERAGE=""
-fi
-
-#
-# Run a python test under python-coverage (if available).
-#
-# Usage:
-#
-#   python_coverage_test module mytest.py [testarg1 testarg2 ...]
-#
-function python_coverage_test {
-  PYTHON_MODULE=$1
-  shift
-
-  if [ "$SKIP_PYTHON_COVERAGE" ] ; then
-    echo "Running python test without coverage because SKIP_PYTHON_COVERAGE=$SKIP_PYTHON_COVERAGE"
-    #run plain test script
-    exec @PYTHON_EXECUTABLE@ "$@"
-  fi
-
-  if [ -n "$COVERAGE" ]; then
-      #run test using python python-coverage tool
-
-      #erase previous results
-      $COVERAGE erase
-
-      #setup python-coverage config file
-      RCFILE=`basename $0`.python-coveragerc
-      printf "$COVERAGE_EXCLUDE_LINES" > $RCFILE
-
-      $COVERAGE run --rcfile $RCFILE --branch "$@"
-      RESULT=$?
-      if [ $RESULT -eq 0 ]; then
-          echo " *** Code python-coverage results *** "
-          $COVERAGE report -m
-          echo " *** End python-coverage results *** "
-      fi
-      exit $RESULT
-  else
-      #python-coverage not available
-      echo "Please run: 'pip3 install coverage' to enable code coverage reporting of the unit tests"
-      #run plain test script
-      exec @PYTHON_EXECUTABLE@ "$@"
-  fi
-}
-
diff --git a/tests/requirements.txt b/tests/requirements.txt
index f14d0b907ce977320ba250046dfab52a3101800a..a32343a5687fd115d6c806f0ca3d27917718eade 100644
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -5,3 +5,4 @@ flake8 >= 5.0.0 # MIT
 pylint >= 2.15.0 # GPLv2
 pytest >= 7.0.0 # MIT
 pytest-cov >= 3.0.0 # MIT
+testing.postgresql >= 1.1 # Apache 2
diff --git a/tests/t_cache.sh b/tests/t_cache.sh
deleted file mode 100755
index 8b7a9ab64fb20592ec98723b5c37b1bac86aa1ae..0000000000000000000000000000000000000000
--- a/tests/t_cache.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_cache
diff --git a/tests/t_cep4_utils.run b/tests/t_cep4_utils.run
deleted file mode 100755
index dbbadd78378910833774ca467f8a7008a126b7ee..0000000000000000000000000000000000000000
--- a/tests/t_cep4_utils.run
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-# Run the unit test
-source python-coverage.sh
-python_coverage_test "*cep4_utils*" t_cep4_utils.py
-
diff --git a/tests/t_cep4_utils.sh b/tests/t_cep4_utils.sh
deleted file mode 100755
index 9298df51c1b5e4c48c03b7d15833c6e1806ed4ee..0000000000000000000000000000000000000000
--- a/tests/t_cep4_utils.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/sh
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-./runctest.sh t_cep4_utils
diff --git a/tests/t_cobaltblocksize.run b/tests/t_cobaltblocksize.run
deleted file mode 100755
index 4841812eadce96cde245480acb160b6f1b89a1fa..0000000000000000000000000000000000000000
--- a/tests/t_cobaltblocksize.run
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-
-source python-coverage.sh
-python_coverage_test "cobaltblocksize*" t_cobaltblocksize.py
diff --git a/tests/t_cobaltblocksize.sh b/tests/t_cobaltblocksize.sh
deleted file mode 100755
index de952a2129968e918dfcacd0d15dde06a7e84c8e..0000000000000000000000000000000000000000
--- a/tests/t_cobaltblocksize.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-./runctest.sh t_cobaltblocksize
diff --git a/tests/t_dbcredentials.run b/tests/t_dbcredentials.run
deleted file mode 100755
index 9497e5890832722cbf5692f586e5aac25c6aac1d..0000000000000000000000000000000000000000
--- a/tests/t_dbcredentials.run
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-source python-coverage.sh
-
-python_coverage_test dbcredentials t_dbcredentials.py
diff --git a/tests/t_dbcredentials.sh b/tests/t_dbcredentials.sh
deleted file mode 100755
index 16dd2c87db63a6b4c0a82cb045679cc6926c99ca..0000000000000000000000000000000000000000
--- a/tests/t_dbcredentials.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_dbcredentials
diff --git a/tests/t_defaultmailaddresses.run b/tests/t_defaultmailaddresses.run
deleted file mode 100755
index cad0a43c04f38d64f888267601ba8e98068cb58b..0000000000000000000000000000000000000000
--- a/tests/t_defaultmailaddresses.run
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-source python-coverage.sh
-
-python_coverage_test defaultmailaddresses t_defaultmailaddresses.py
diff --git a/tests/t_defaultmailaddresses.sh b/tests/t_defaultmailaddresses.sh
deleted file mode 100755
index 498398c515e12f66e00d316047f67d55d386f361..0000000000000000000000000000000000000000
--- a/tests/t_defaultmailaddresses.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_defaultmailaddresses
diff --git a/tests/t_json_utils.run b/tests/t_json_utils.run
deleted file mode 100755
index 4ef087e0334221bf9ec7a2cca2a4344047664805..0000000000000000000000000000000000000000
--- a/tests/t_json_utils.run
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-
-source python-coverage.sh
-python_coverage_test "*json_utils*" t_json_utils.py
-
diff --git a/tests/t_json_utils.sh b/tests/t_json_utils.sh
deleted file mode 100755
index 3152fa0d6ed297257abb4959116c25107b5f15d1..0000000000000000000000000000000000000000
--- a/tests/t_json_utils.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_json_utils
diff --git a/tests/t_methodtrigger.sh b/tests/t_methodtrigger.sh
deleted file mode 100755
index c786a2cbcdafae4571c5b6fba84fa21c96381ad6..0000000000000000000000000000000000000000
--- a/tests/t_methodtrigger.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_methodtrigger
diff --git a/tests/t_parameterset.run b/tests/t_parameterset.run
deleted file mode 100755
index b2be23d31a5770f1dd56c4eb0c5c1d9cfe84c3b1..0000000000000000000000000000000000000000
--- a/tests/t_parameterset.run
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-source python-coverage.sh
-
-python_coverage_test parameterset t_parameterset.py
diff --git a/tests/t_parameterset.sh b/tests/t_parameterset.sh
deleted file mode 100755
index eb7763ac61c926a33d5c01ecd8ef629b50e11755..0000000000000000000000000000000000000000
--- a/tests/t_parameterset.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_parameterset
diff --git a/tests/t_postgres.run b/tests/t_postgres.run
deleted file mode 100755
index 05c53f094a11a78c708480291e70cdda3c25a44f..0000000000000000000000000000000000000000
--- a/tests/t_postgres.run
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-source python-coverage.sh
-
-python_coverage_test postgres t_postgres.py
diff --git a/tests/t_postgres.sh b/tests/t_postgres.sh
deleted file mode 100755
index 34699f2f49dfbe079eaa1fb5d7068c6dcfc699ab..0000000000000000000000000000000000000000
--- a/tests/t_postgres.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_postgres
diff --git a/tests/t_test_utils.run b/tests/t_test_utils.run
deleted file mode 100755
index f3e1ccfbc0b943cfc8abb88ba1764f1a06f7a65b..0000000000000000000000000000000000000000
--- a/tests/t_test_utils.run
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-source python-coverage.sh
-
-python_coverage_test test_utils t_test_utils.py
diff --git a/tests/t_test_utils.sh b/tests/t_test_utils.sh
deleted file mode 100755
index 70c850d268e318120c4d4fd3c1dc7281ac6aa29b..0000000000000000000000000000000000000000
--- a/tests/t_test_utils.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_test_utils
diff --git a/tests/t_toposort.sh b/tests/t_toposort.sh
deleted file mode 100755
index 2730b2470b6729f33d025d595fda057625e1cae6..0000000000000000000000000000000000000000
--- a/tests/t_toposort.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_toposort
diff --git a/tests/t_typing.run b/tests/t_typing.run
deleted file mode 100755
index 6bc23fadc736235c1143d3317d88307ffeac0f67..0000000000000000000000000000000000000000
--- a/tests/t_typing.run
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-
-source python-coverage.sh
-python_coverage_test "*json_utils*" t_typing.py
-
diff --git a/tests/t_typing.sh b/tests/t_typing.sh
deleted file mode 100755
index d788f5a03bee1f34f0c524afadfee796de8e081a..0000000000000000000000000000000000000000
--- a/tests/t_typing.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_typing
diff --git a/tests/t_util.run b/tests/t_util.run
deleted file mode 100755
index ba348910d65e902f142c7f10c6488b557b0f6e77..0000000000000000000000000000000000000000
--- a/tests/t_util.run
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/bash
-source python-coverage.sh
-
-python_coverage_test util t_util.py
diff --git a/tests/t_util.sh b/tests/t_util.sh
deleted file mode 100755
index 02ec67618d566c80a229bb26eaebb7e9cc9d2b5b..0000000000000000000000000000000000000000
--- a/tests/t_util.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/sh
-./runctest.sh t_util
diff --git a/tests/t_cache.py b/tests/test_cache.py
similarity index 90%
rename from tests/t_cache.py
rename to tests/test_cache.py
index 2cf8ef0aa0124571700fa6c0ebd56e302866d2ab..6b5d619168347236d6af7ed7ae9bf0473b5ac3f9 100644
--- a/tests/t_cache.py
+++ b/tests/test_cache.py
@@ -1,8 +1,14 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 import unittest
-from lofar.common.cache import cache
-from lofar.common.test_utils import unit_test, exit_with_skipped_code_if_skip_unit_tests
+
+from pycommon.cache import cache
+from pycommon.test_utils import unit_test, exit_with_skipped_code_if_skip_unit_tests
+
 exit_with_skipped_code_if_skip_unit_tests()
 
+
 class TestCache(unittest.TestCase):
 
     @cache
@@ -97,12 +103,3 @@ class TestCache(unittest.TestCase):
 
         self.assertEqual(result, (1, 3))
         self.assertEqual(self.invocations, 2)
-
-def main(argv):
-    unittest.main()
-
-if __name__ == "__main__":
-    # run all tests
-    import sys
-    main(sys.argv[1:])
-
diff --git a/tests/t_cep4_utils.py b/tests/test_cep4_utils.py
similarity index 76%
rename from tests/t_cep4_utils.py
rename to tests/test_cep4_utils.py
index 5db799427443dcd3c2e1eaaae5b223e6c941a364..5d56202b8f9e2dea01ca2237f8a04107f958b7aa 100755
--- a/tests/t_cep4_utils.py
+++ b/tests/test_cep4_utils.py
@@ -1,34 +1,19 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 import unittest
 from subprocess import call
-
 import logging
-from lofar.common.cep4_utils import *
-from lofar.common.test_utils import integration_test
 
-from lofar.common.test_utils import exit_with_skipped_code_if_skip_integration_tests
-exit_with_skipped_code_if_skip_integration_tests()
+from pycommon.cep4_utils import *
+from pycommon.test_utils import integration_test
+from pycommon.test_utils import exit_with_skipped_code_if_skip_integration_tests
 
+exit_with_skipped_code_if_skip_integration_tests()
+logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG)
 logger = logging.getLogger(__name__)
 
+
 @integration_test
 class TestCep4Utils(unittest.TestCase):
 
@@ -104,8 +89,3 @@ class TestCep4Utils(unittest.TestCase):
         #redirect stdout/stderr to /dev/null
         with open('/dev/null', 'w') as dev_null:
             self.assertEqual(0, call(cmd, stdout=dev_null, stderr=dev_null))
-
-if __name__ == '__main__':
-    logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.DEBUG)
-
-    unittest.main()
diff --git a/tests/t_cobaltblocksize.py b/tests/test_cobaltblocksize.py
similarity index 93%
rename from tests/t_cobaltblocksize.py
rename to tests/test_cobaltblocksize.py
index 62931b92c6f4e28c05d0a9d9bd7418ae3c5c5b41..33d9b890332d00bd639d72622836aa04f260e787 100644
--- a/tests/t_cobaltblocksize.py
+++ b/tests/test_cobaltblocksize.py
@@ -1,11 +1,18 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 import unittest
-from lofar.common.cobaltblocksize import StokesSettings, CorrelatorSettings, BlockConstraints, BlockSize
 import logging
-from lofar.common.test_utils import unit_test
+
+from pycommon.cobaltblocksize import (
+    StokesSettings, CorrelatorSettings, BlockConstraints, BlockSize
+)
+from pycommon.test_utils import unit_test
 
 logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
 logger = logging.getLogger(__name__)
 
+
 class TestBlockConstraints(unittest.TestCase):
 
     @unit_test
@@ -126,7 +133,3 @@ class TestBlockSize(unittest.TestCase):
 
         # we're happy if a valid block size was found
         self.assertNotEqual(bs.blockSize, None)
-
-
-if __name__ == "__main__":
-    unittest.main(verbosity=2)
diff --git a/tests/test_cool_module.py b/tests/test_cool_module.py
deleted file mode 100644
index c347632cabb32340680a0ee663c5c8892adf9e81..0000000000000000000000000000000000000000
--- a/tests/test_cool_module.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#  Copyright (C) 2023 ASTRON (Netherlands Institute for Radio Astronomy)
-#  SPDX-License-Identifier: Apache-2.0
-
-"""Testing of the Cool Module"""
-from unittest import TestCase
-
-from pycommon.cool_module import greeter
-
-
-class TestCoolModule(TestCase):
-    """Test Case of the Cool Module"""
-
-    def test_greeter(self):
-        """Testing that the greeter does not crash"""
-        greeter()
-        self.assertEqual(2 + 2, 4)
diff --git a/tests/t_dbcredentials.py b/tests/test_dbcredentials.py
similarity index 91%
rename from tests/t_dbcredentials.py
rename to tests/test_dbcredentials.py
index d2693657507b82b09fd5f988241614b191bce3c1..52b3f84097993a94ad0716e6cb20c41a0b7a9c71 100644
--- a/tests/t_dbcredentials.py
+++ b/tests/test_dbcredentials.py
@@ -1,15 +1,18 @@
-#!/usr/bin/env python3
-
-from lofar.common.test_utils import exit_with_skipped_code_if_skip_unit_tests
-exit_with_skipped_code_if_skip_unit_tests()
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 import os
 import unittest
 import tempfile
 from uuid import uuid4
-from lofar.common.dbcredentials import *
 from configparser import NoSectionError
-from lofar.common.test_utils import unit_test
+
+from pycommon.test_utils import exit_with_skipped_code_if_skip_unit_tests
+from pycommon.test_utils import unit_test
+from pycommon.dbcredentials import *
+
+exit_with_skipped_code_if_skip_unit_tests()
+
 
 def setUpModule():
   pass
@@ -144,11 +147,3 @@ test = word word
     # test if the free-form config options got through
     self.assertEqual(c_out.config["foo"], "bar")
     self.assertEqual(c_out.config["test"], "word word")
-
-def main(argv):
-  unittest.main()
-
-if __name__ == "__main__":
-  # run all tests
-  import sys
-  main(sys.argv[1:])
diff --git a/tests/t_defaultmailaddresses.py b/tests/test_defaultmailaddresses.py
similarity index 81%
rename from tests/t_defaultmailaddresses.py
rename to tests/test_defaultmailaddresses.py
index 315cfb68f7747a7e474b27612c9b585dfe030c8a..cc2b9145c0d98966ed8fbefa3c09b9bc5bd7eece 100644
--- a/tests/t_defaultmailaddresses.py
+++ b/tests/test_defaultmailaddresses.py
@@ -1,12 +1,15 @@
-#!/usr/bin/env python3
-
-from lofar.common.test_utils import exit_with_skipped_code_if_skip_unit_tests
-exit_with_skipped_code_if_skip_unit_tests()
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 import unittest
 import tempfile
-from lofar.common.defaultmailaddresses import PipelineEmailConfig
-from lofar.common.test_utils import unit_test
+
+from pycommon.test_utils import exit_with_skipped_code_if_skip_unit_tests
+from pycommon.test_utils import unit_test
+from pycommon.defaultmailaddresses import PipelineEmailConfig
+
+exit_with_skipped_code_if_skip_unit_tests()
+
 
 def setUpModule():
   pass
@@ -57,12 +60,3 @@ error-sender
         pec = PipelineEmailConfig(filepatterns=[f.name])
         with self.assertRaises(Exception):
             print(pec["error-sender"])
-        
-
-def main():
-  unittest.main()
-
-if __name__ == "__main__":
-  # run all tests
-  import sys
-  main()
diff --git a/tests/t_json_utils.py b/tests/test_json_utils.py
similarity index 94%
rename from tests/t_json_utils.py
rename to tests/test_json_utils.py
index 4dbadeead071ea81839fd5b39c1cabf424ab22a7..72e257e8832fdc9d8729f4e2177c62e4ffb7018d 100755
--- a/tests/t_json_utils.py
+++ b/tests/test_json_utils.py
@@ -1,33 +1,21 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
-
-from lofar.common.test_utils import exit_with_skipped_code_if_skip_unit_tests
-exit_with_skipped_code_if_skip_unit_tests()
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
+import unittest
+import threading
+import json
 import logging
+
+from pycommon.test_utils import exit_with_skipped_code_if_skip_unit_tests
+from pycommon.json_utils import (
+    get_default_json_object_for_schema, replace_host_in_urls, resolved_remote_refs,
+    resolved_local_refs, get_sub_schema, raise_on_self_refs
+)
+
+exit_with_skipped_code_if_skip_unit_tests()
 logger = logging.getLogger(__name__)
 logging.basicConfig(format='%(asctime)s %(process)s %(threadName)s %(levelname)s %(message)s', level=logging.DEBUG)
 
-import unittest
-import threading
-import json
-from lofar.common.json_utils import get_default_json_object_for_schema, replace_host_in_urls, resolved_remote_refs, resolved_local_refs, get_sub_schema, raise_on_self_refs, resolved_refs
 
 class TestJSONUtils(unittest.TestCase):
     def setUp(self) -> None:
@@ -241,7 +229,7 @@ class TestJSONUtils(unittest.TestCase):
         '''test if $refs to URL's are properly resolved'''
         import http.server
         import socketserver
-        from lofar.common.util import find_free_port
+        from pycommon.util import find_free_port
 
         port = find_free_port(8000, allow_reuse_of_lingering_port=False)
         host = "127.0.0.1"
@@ -407,7 +395,7 @@ class TestJSONUtils(unittest.TestCase):
         '''test if ambiguous $refs raise'''
         import http.server
         import socketserver
-        from lofar.common.util import find_free_port
+        from pycommon.util import find_free_port
 
         port = find_free_port(8000, allow_reuse_of_lingering_port=False)
         host = "127.0.0.1"
@@ -518,5 +506,3 @@ class TestJSONUtils(unittest.TestCase):
         self.assertEqual("http://json-schema.org/draft-06/schema#", url_fixed_schema['$schema'])
         self.assertEqual(json.dumps(schema, indent=2).replace(base_host, new_base_host), json.dumps(url_fixed_schema, indent=2))
 
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/t_methodtrigger.py b/tests/test_methodtrigger.py
similarity index 90%
rename from tests/t_methodtrigger.py
rename to tests/test_methodtrigger.py
index 064085e7077ca3c98b1231b840baaa673e041479..ad085a83362ad7360581d35b73b752ef788b2a05 100644
--- a/tests/t_methodtrigger.py
+++ b/tests/test_methodtrigger.py
@@ -1,12 +1,16 @@
-from lofar.common.test_utils import exit_with_skipped_code_if_skip_unit_tests
-exit_with_skipped_code_if_skip_unit_tests()
-
-import unittest
-from lofar.common.methodtrigger import MethodTrigger
-from lofar.common.test_utils import unit_test
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 from threading import Thread
 import time
+import unittest
+
+from pycommon.test_utils import exit_with_skipped_code_if_skip_unit_tests
+from pycommon.methodtrigger import MethodTrigger
+from pycommon.test_utils import unit_test
+
+exit_with_skipped_code_if_skip_unit_tests()
+
 
 class TestMethodTrigger(unittest.TestCase):
   def setUp(self):
@@ -124,12 +128,3 @@ class TestArgs(unittest.TestCase):
     # Check stored arguments
     self.assertEqual(self.trigger.args, (1, 2))
     self.assertEqual(self.trigger.kwargs, {"c": 3, "d": 4})
-
-def main(argv):
-  unittest.main()
-
-if __name__ == "__main__":
-  # run all tests
-  import sys
-  main(sys.argv[1:])
-
diff --git a/tests/t_parameterset.py b/tests/test_parameterset.py
similarity index 97%
rename from tests/t_parameterset.py
rename to tests/test_parameterset.py
index 40af91b81b391c64cebe9d18f4f3dde628526ac3..11cbd74ab949cf5d7602e8ba3900808d23e9444f 100644
--- a/tests/t_parameterset.py
+++ b/tests/test_parameterset.py
@@ -1,6 +1,6 @@
 from unittest import TestCase
 
-from lofar.common.parameterset import parameterset
+from pycommon.parameterset import parameterset
 
 
 class TestParameterSet(TestCase):
diff --git a/tests/t_postgres.py b/tests/test_postgres.py
similarity index 93%
rename from tests/t_postgres.py
rename to tests/test_postgres.py
index 91e298cfcdc66a93e7c069a0552283b6f460f02d..f67f0f07e3dc3fba08d9c329f652cb4d76011d66 100755
--- a/tests/t_postgres.py
+++ b/tests/test_postgres.py
@@ -1,20 +1,24 @@
-#!/usr/bin/env python3
-
-from lofar.common.test_utils import exit_with_skipped_code_if_skip_integration_tests
-exit_with_skipped_code_if_skip_integration_tests()
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
+import logging
+import signal
+from copy import deepcopy
 import unittest
 from unittest import mock
-from lofar.common.postgres import *
-from lofar.common.testing.postgres import PostgresTestDatabaseInstance, PostgresTestMixin
+
 import psycopg2
-import signal
-from copy import deepcopy
-from lofar.common.test_utils import integration_test
 
-import logging
+from pycommon.test_utils import exit_with_skipped_code_if_skip_integration_tests
+from pycommon.postgres import *
+from pycommon.test_utils import integration_test
+from tests.postgres import PostgresTestDatabaseInstance, PostgresTestMixin
+
+exit_with_skipped_code_if_skip_integration_tests()
+
 logger = logging.getLogger(__name__)
 
+
 class MyPostgresTestDatabaseInstance(PostgresTestDatabaseInstance):
     def apply_database_schema(self):
         # use 'normal' psycopg2 API to connect and setup the database,
@@ -41,7 +45,7 @@ class TestPostgres(MyPostgresTestMixin, unittest.TestCase):
         incorrect_dbcreds.port += 1
 
         # test if connecting fails
-        with mock.patch('lofar.common.database.logger') as mocked_logger:
+        with mock.patch('pycommon.database.logger') as mocked_logger:
             with self.assertRaises(DatabaseConnectionError):
                 NUM_CONNECT_RETRIES = 2
                 with PostgresDatabaseConnection(dbcreds=incorrect_dbcreds, connect_retry_interval=0.1, num_connect_retries=NUM_CONNECT_RETRIES) as db:
@@ -119,6 +123,3 @@ class TestPostgres(MyPostgresTestMixin, unittest.TestCase):
 
 
 logging.basicConfig(format='%(asctime)s %(process)s %(threadName)s %(levelname)s %(message)s', level=logging.DEBUG)
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/t_test_utils.py b/tests/test_test_utils.py
similarity index 88%
rename from tests/t_test_utils.py
rename to tests/test_test_utils.py
index eeabd724a006307eeb231460fa9fe986e8ff5c07..5cf36a3d9cf5dc0d2a3fb784333b7c7563eb696a 100644
--- a/tests/t_test_utils.py
+++ b/tests/test_test_utils.py
@@ -1,11 +1,9 @@
-#!/usr/bin/env python3
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
-from lofar.common.test_utils import exit_with_skipped_code_if_skip_unit_tests
-exit_with_skipped_code_if_skip_unit_tests()
+from pycommon.test_utils import *
 
-import unittest
-import tempfile
-from lofar.common.test_utils import *
+exit_with_skipped_code_if_skip_unit_tests()
 
 def setUpModule():
     pass
@@ -67,12 +65,3 @@ class TestTestUtils(unittest.TestCase):
         xml2 = '<xml><test att="2">test text</test></xml>'
         with self.assertRaises(AssertionError):
             assertEqualXML(xml1, xml2)
-
-
-def main(argv):
-    unittest.main()
-
-if __name__ == "__main__":
-    # run all tests
-    import sys
-    main(sys.argv[1:])
diff --git a/tests/t_toposort.py b/tests/test_toposort.py
similarity index 96%
rename from tests/t_toposort.py
rename to tests/test_toposort.py
index d47841eda23f7d999d0da72dcd77afd4f533b709..f31fd1dac20e87c9a86671b689747b8b7ccea38a 100644
--- a/tests/t_toposort.py
+++ b/tests/test_toposort.py
@@ -1,6 +1,10 @@
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
+
 import unittest
-from lofar.common.toposort import Graph, toposorted
-from lofar.common.test_utils import unit_test
+
+from pycommon.toposort import Graph, toposorted
+from pycommon.test_utils import unit_test
 
 
 class TestGraph(unittest.TestCase):
@@ -153,7 +157,7 @@ class TestToposort(unittest.TestCase):
 
     @unit_test
     def test_disconnected_graph(self):
-        # 5 -> 4 -> 3 
+        # 5 -> 4 -> 3
         # 2 -> 1 -> 0
         g = Graph([0,1,2,3,4,5])
         g.add_edge(5, 4)
@@ -203,12 +207,3 @@ class TestToposort(unittest.TestCase):
 
         result = toposorted(g)
         self.assertTopoSorted(g, result)
-
-def main(argv):
-    unittest.main()
-
-if __name__ == "__main__":
-    # run all tests
-    import sys
-    main(sys.argv[1:])
-
diff --git a/tests/t_typing.py b/tests/test_typing.py
similarity index 81%
rename from tests/t_typing.py
rename to tests/test_typing.py
index b88641d6b31989a3c2ce087e775fa9c0bec872e5..63232438a2d7bcd0a0383e3f93fce9b56a03efae 100755
--- a/tests/t_typing.py
+++ b/tests/test_typing.py
@@ -1,29 +1,14 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2012-2015  ASTRON (Netherlands Institute for Radio Astronomy)
-# P.O. Box 2, 7990 AA Dwingeloo, The Netherlands
-#
-# This file is part of the LOFAR software suite.
-# The LOFAR software suite is free software: you can redistribute it and/or
-# modify it under the terms of the GNU General Public License as published
-# by the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# The LOFAR software suite is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with the LOFAR software suite. If not, see <http://www.gnu.org/licenses/>.
+#  Copyright (C) 2012 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
+import unittest
 import logging
+
+from pycommon.typing import check_type_hints
+
 logger = logging.getLogger(__name__)
 logging.basicConfig(format='%(asctime)s %(process)s %(threadName)s %(levelname)s %(message)s', level=logging.DEBUG)
 
-from lofar.common.typing import check_type_hints
-
-import unittest
 
 class TestCheckTypeHints(unittest.TestCase):
     def test_no_argument(self):
@@ -172,6 +157,3 @@ class TestCheckTypeHints(unittest.TestCase):
 
         with self.assertRaises(TypeError):
             myfunc(None)
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/tests/t_util.py b/tests/test_util.py
similarity index 76%
rename from tests/t_util.py
rename to tests/test_util.py
index c54747c3af0e1fc45e9532a59bd6b420c678cf38..41528e201b415aace3da1d9c0376ec44df15957d 100644
--- a/tests/t_util.py
+++ b/tests/test_util.py
@@ -1,12 +1,14 @@
-#!/usr/bin/env python3
-
-from lofar.common.test_utils import exit_with_skipped_code_if_skip_unit_tests
-exit_with_skipped_code_if_skip_unit_tests()
+#  Copyright (C) 2024 ASTRON (Netherlands Institute for Radio Astronomy)
+#  SPDX-License-Identifier: GPL-3.0-or-later
 
 import unittest
-import tempfile
-from lofar.common.util import *
-from lofar.common.test_utils import unit_test
+
+from pycommon.test_utils import exit_with_skipped_code_if_skip_unit_tests
+from pycommon.test_utils import unit_test
+
+from pycommon.util import *
+
+exit_with_skipped_code_if_skip_unit_tests()
 
 def setUpModule():
     pass
@@ -38,11 +40,11 @@ class TestUtils(unittest.TestCase):
         self.assertFalse(is_iterable(None))
 
     def test_merge_nested_dicts(self):
-        dict_a = {'a': 1, 
+        dict_a = {'a': 1,
                   'b': {
-                      'c': 3, 
-                      'd': [4, 5, 6]}, 
-                  'e': [{'foo': 1}, 
+                      'c': 3,
+                      'd': [4, 5, 6]},
+                  'e': [{'foo': 1},
                         {'bar': 2}]}
 
         dict_b = {'a': 2}
@@ -60,11 +62,3 @@ class TestUtils(unittest.TestCase):
         dict_b = {'e': [{'foo': 2}, {'bar': 3}]}
         merged = dict_with_overrides(dict_a, dict_b)
         self.assertEqual({'a': 1, 'b': {'c': 3, 'd': [4, 5, 6]}, 'e': [{'foo': 2}, {'bar': 3}]}, merged)
-
-def main(argv):
-    unittest.main()
-
-if __name__ == "__main__":
-    # run all tests
-    import sys
-    main(sys.argv[1:])
diff --git a/tox.ini b/tox.ini
index f72b92ab1021690122004d3334ea287f0a01c003..23099c4772074e53e34a9493765cc4d45b480884 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,7 +1,9 @@
 [tox]
 # Generative environment list to test all supported Python versions
-envlist = py3{8,9,10,11,12},black,pep8,pylint
-minversion = 3.18.0
+envlist = py3{10,11,12,13}
+min_version = 4.3.3
+requires =
+    tox-ignore-env-name-mismatch ~= 0.2.0
 
 [testenv]
 usedevelop = True
@@ -9,8 +11,6 @@ package = wheel
 wheel_build_env = .pkg
 
 setenv =
-    LANGUAGE=en_US
-    LC_ALL=en_US.UTF-8
     PYTHONWARNINGS=default::DeprecationWarning
 deps =
     -r{toxinidir}/requirements.txt
@@ -40,17 +40,6 @@ commands =
     format: {envpython} -m autopep8 -v -aa --in-place --recursive tests
     format: {envpython} -m black -v pycommon tests
 
-[testenv:docs]
-; unset LC_ALL / LANGUAGE from testenv, would fail sphinx otherwise
-setenv =
-deps =
-    -r{toxinidir}/requirements.txt
-    -r{toxinidir}/docs/requirements.txt
-changedir = {toxinidir}
-commands =
-    {envpython} docs/cleanup.py
-    sphinx-build -b html docs/source docs/build/html
-
 [testenv:build]
 usedevelop = False
 deps = build