Repository: EUDAT-B2HANDLE/PYHANDLE (language: python)

Issue:
check_handle_syntax fails with unhelpful error message when "hdl:prefix/suffix" is passed
When I call the method `check_handle_syntax(string)` with the handle `hdl:21.140x/bla` I get this error message:

> pyhandle.handleexceptions.HandleSyntaxError: Handle hdl:21.1410x/bla does not have expected syntax: Index is not an integer.
> Expected: index:prefix/suffix

But  `hdl:21.140x/bla` is a perfectly valid handle. At least, the error message should be more helpful.

Because of the colon `:`, the method assumes we're trying to pass a single field, i.e. with an index in front. That does not make sense to me, as we're checking the syntax of a handle.

Where?

* https://github.com/EUDAT-B2HANDLE/PYHANDLE/blob/1f3e075c5eef40f78970a0d3d57a933a1baf1818/pyhandle/utilhandle.py
* https://github.com/EUDAT-B2HANDLE/PYHANDLE/blob/1f3e075c5eef40f78970a0d3d57a933a1baf1818/pyhandle/utilhandle.py#L66
Add step of Dependencies



Discussion / hints:

Fix patch:
diff --git a/.gitconfig b/.gitconfig
new file mode 100755
index 0000000..e69de29
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 1d7d515..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,17 +0,0 @@
-# vim:set ft=dockerfile:
-FROM       debian:jessie
-
-RUN        apt-get update && apt-get install -y --no-install-recommends \
-           ca-certificates \
-           python \
-           python-setuptools \
-        && apt-get clean \
-        && rm -rf /var/lib/apt/lists/*
-
-ADD        . /opt/PYHANDLE
-
-WORKDIR    /opt/PYHANDLE
-
-RUN python setup.py install
-
-# TODO: Document usage of this Dockerfile.
\ No newline at end of file
diff --git a/Dockerfile-py3.5 b/Dockerfile-py3.5
deleted file mode 100644
index 928d820..0000000
--- a/Dockerfile-py3.5
+++ /dev/null
@@ -1,100 +0,0 @@
-# vim:set ft=dockerfile:
-FROM       debian:jessie
-
-# See https://github.com/docker-library/python/blob/master/3.5/Dockerfile
-# Build:
-#docker build -t eudat-pyhandle:py3.5 -f Dockerfile-py3.5 .
-
-# ensure local python is preferred over distribution python
-ENV PATH /usr/local/bin:$PATH
-
-# http://bugs.python.org/issue19846
-# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
-ENV LANG C.UTF-8
-
-# runtime dependencies
-RUN apt-get update && apt-get install -y --no-install-recommends \
-           ca-certificates \
-           wget \
-           autoconf \
-           automake \
-           bzip2 \
-           file \
-           g++ \
-           gcc \
-           libssl-dev \
-           libtool \
-           make \
-           patch \
-           xz-utils \
-           tcl \
-           tk \
-        && rm -rf /var/lib/apt/lists/*
-
-ENV GPG_KEY 97FC712E4C024BBEA48A61ED3A5CA953F73C700D
-ENV PYTHON_VERSION 3.5.3
-
-# if this is called "PIP_VERSION", pip explodes with "ValueError: invalid truth value '<VERSION>'"
-ENV PYTHON_PIP_VERSION 9.0.1
-
-RUN set -ex \
-        && buildDeps=' \
-                tcl-dev \
-                tk-dev \
-        ' \
-        && apt-get update && apt-get install -y $buildDeps --no-install-recommends && rm -rf /var/lib/apt/lists/* \
-        \
-        && wget -O python.tar.xz "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz" \
-        && wget -O python.tar.xz.asc "https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc" \
-        && export GNUPGHOME="$(mktemp -d)" \
-        && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$GPG_KEY" \
-        && gpg --batch --verify python.tar.xz.asc python.tar.xz \
-        && rm -r "$GNUPGHOME" python.tar.xz.asc \
-        && mkdir -p /usr/src/python \
-        && tar -xJC /usr/src/python --strip-components=1 -f python.tar.xz \
-        && rm python.tar.xz \
-        \
-        && cd /usr/src/python \
-        && ./configure \
-                --enable-loadable-sqlite-extensions \
-                --enable-shared \
-        && make -j$(nproc) \
-        && make install \
-        && ldconfig \
-        \
-# explicit path to "pip3" to ensure distribution-provided "pip3" cannot interfere
-        && if [ ! -e /usr/local/bin/pip3 ]; then : \
-                && wget -O /tmp/get-pip.py 'https://bootstrap.pypa.io/get-pip.py' \
-                && python3 /tmp/get-pip.py "pip==$PYTHON_PIP_VERSION" \
-                && rm /tmp/get-pip.py \
-        ; fi \
-# we use "--force-reinstall" for the case where the version of pip we're trying to install is the same as the version bundled with Python
-# ("Requirement already up-to-date: pip==8.1.2 in /usr/local/lib/python3.6/site-packages")
-# https://github.com/docker-library/python/pull/143#issuecomment-241032683
-        && pip3 install --no-cache-dir --upgrade --force-reinstall "pip==$PYTHON_PIP_VERSION" \
-# then we use "pip list" to ensure we don't have more than one pip version installed
-# https://github.com/docker-library/python/pull/100
-        && [ "$(pip list |tac|tac| awk -F '[ ()]+' '$1 == "pip" { print $2; exit }')" = "$PYTHON_PIP_VERSION" ] \
-        \
-        && find /usr/local -depth \
-                \( \
-                        \( -type d -a -name test -o -name tests \) \
-                        -o \
-                        \( -type f -a -name '*.pyc' -o -name '*.pyo' \) \
-                \) -exec rm -rf '{}' + \
-        && apt-get purge -y --auto-remove $buildDeps \
-        && rm -rf /usr/src/python ~/.cache
-
-# make some useful symlinks that are expected to exist
-RUN cd /usr/local/bin \
-        && { [ -e easy_install ] || ln -s easy_install-* easy_install; } \
-        && ln -s idle3 idle \
-        && ln -s pydoc3 pydoc \
-        && ln -s python3 python \
-        && ln -s python3-config python-config
-
-ADD        . /opt/PYHANDLE
-
-WORKDIR    /opt/PYHANDLE
-
-RUN        python setup.py install
diff --git a/Dockerfile-python2.6 b/Dockerfile-python2.6
deleted file mode 100644
index 8a7997f..0000000
--- a/Dockerfile-python2.6
+++ /dev/null
@@ -1,20 +0,0 @@
-# vim:set ft=dockerfile:
-FROM       centos:6
-
-# Build fails:
-#docker build -t eudat-pyhandle:py2.6 -f Dockerfile-python2.6 .
-# TODO FIXME Fix this, or remove if unfixable (after all, it's py 2.6...)
-
-RUN        yum -y update && yum clean all && yum -y install \
-           python-setuptools
-RUN 	   yum -y install wget
-RUN 	   wget https://bootstrap.pypa.io/get-pip.py
-RUN 	   python get-pip.py
-RUN        pip install -U pip setuptools
-ADD        . /opt/PYHANDLE
-
-WORKDIR    /opt/PYHANDLE
-
-RUN python setup.py install
-
-# TODO: Fix this Dockerfile.
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..18e9e4d
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,148 @@
+pipeline {
+    agent any
+    options {
+        checkoutToSubdirectory('PYHANDLE')
+    }
+    environment {
+        PROJECT_DIR="PYHANDLE"
+        GH_USER = 'newgrnetci'
+        GH_EMAIL = '<argo@grnet.gr>'
+        
+    }
+    stages {
+        stage ('Run tests for each python version') {
+            parallel {
+                stage ('Test python 3.5') {
+                    agent {
+                        dockerfile {
+                            filename "pyhandle/tests/testdockers/Dockerfile-py3.5"
+                            dir "$PROJECT_DIR"
+                            additionalBuildArgs "-t eudat-pyhandle:py3.5"
+                            args "-u root:root"
+                        }
+                    }
+                    steps {
+                        sh '''
+                            cd $WORKSPACE/$PROJECT_DIR/pyhandle/tests
+                            ./docker-entrypoint.sh coverage
+                        '''
+                        cobertura coberturaReportFile: '**/coverage.xml'
+                    }
+                }
+                stage ('Test python 3.6') {
+                    agent {
+                        dockerfile {
+                            filename "pyhandle/tests/testdockers/Dockerfile-py3.6"
+                            dir "$PROJECT_DIR"
+                            additionalBuildArgs "-t eudat-pyhandle:py3.6"
+                            args "-u root:root"
+                        }
+                    }
+                    steps {
+                        sh '''
+                            cd $WORKSPACE/$PROJECT_DIR/pyhandle/tests
+                            ./docker-entrypoint.sh coverage
+                        '''
+                        cobertura coberturaReportFile: '**/coverage.xml'
+                    }
+                }
+                stage ('Test python 3.7') {
+                    agent {
+                        dockerfile {
+                            filename "pyhandle/tests/testdockers/Dockerfile-py3.7"
+                            dir "$PROJECT_DIR"
+                            additionalBuildArgs "-t eudat-pyhandle:py3.7"
+                            args "-u root:root"
+                        }
+                    }
+                    steps {
+                        sh '''
+                            cd $WORKSPACE/$PROJECT_DIR/pyhandle/tests
+                            ./docker-entrypoint.sh coverage
+                        '''
+                        cobertura coberturaReportFile: '**/coverage.xml'
+                    }
+                }
+                 stage ('Test python 3.9') {
+                    agent {
+                        dockerfile {
+                            filename "pyhandle/tests/testdockers/Dockerfile-py3.9"
+                            dir "$PROJECT_DIR"
+                            additionalBuildArgs "-t eudat-pyhandle:py3.9"
+                            args "-u root:root"
+                        }
+                    }
+                    steps {
+                        sh '''
+                            cd $WORKSPACE/$PROJECT_DIR/pyhandle/tests
+                            ./docker-entrypoint.sh coverage
+                        '''
+                        cobertura coberturaReportFile: '**/coverage.xml'
+                    }
+                }
+                stage ('Test python 3.10') {
+                    agent {
+                        dockerfile {
+                            filename "pyhandle/tests/testdockers/Dockerfile-py3.10"
+                            dir "$PROJECT_DIR"
+                            additionalBuildArgs "-t eudat-pyhandle:py3.10"
+                            args "-u root:root"
+                        }
+                    }
+                    steps {
+                        sh '''
+                            cd $WORKSPACE/$PROJECT_DIR/pyhandle/tests
+                            ./docker-entrypoint-310.sh coverage
+                        '''
+                        cobertura coberturaReportFile: '**/coverage.xml'
+                    }
+                }
+            }
+       }
+        
+       stage ('Deploy Docs') {
+           when {
+                changeset 'docs/source/**'
+           }   
+           agent {
+                docker {
+                    image 'eudat-pyhandle:py3.10'
+                
+                }
+            }
+            steps {
+            
+                echo 'Sending to gh-pages...'
+                sshagent (credentials: ['jenkins-master']) {
+                    sh '''
+                         
+                        ls ~/
+                        mkdir ~/.ssh && ssh-keyscan -H github.com > ~/.ssh/known_hosts
+                        git config --global user.email ${GH_EMAIL}
+                        git config --global user.name ${GH_USER}
+                        GIT_USER=${GH_USER} 
+                        USE_SSH=true 
+                        cd $WORKSPACE/$PROJECT_DIR
+                        cd docs
+                        make html
+                        cd $WORKSPACE/$PROJECT_DIR/docs/build/html
+                        touch .nojekyll
+                        rm -rf .git   
+                        git init
+                        git remote add deploy git@github.com:EUDAT-B2HANDLE/PYHANDLE
+                        git checkout -b gh-pages
+                        git add .
+                        git commit -am "docs update"
+                        git push deploy gh-pages --force
+                        rm -rf .git                        
+                    '''
+                }
+            }
+       }
+    }
+    post {
+        always {
+            cleanWs()
+        }
+    }
+} 
diff --git a/README.md b/README.md
index b954a07..b6523d8 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,7 @@ As [B2Handle](https://github.com/EUDAT-B2SAFE/B2HANDLE) was developed with a spe
 
 
 
+
 # Installation
 
 You can install the PyHandle library as follows:
@@ -27,6 +28,21 @@ The library is also available on PyPi and can be installed via pip:
 ```
 
 For more information on the methods offered by the library, please consult the [technical documentation](http://eudat-b2safe.github.io/PYHANDLE/).
+
+## Instantiate:
+
+One of the core steps is to instantiate the file with the needed credentials. 
+A guide on how to use them is the following: 
+
+```
+credentials_file = './credentials/creds.json'
+# Path must be relative to current working dir
+# JSON file must contain absolute paths, or paths relative to the creds.json file!!
+creds = pyhandle.clientcredentials.PIDClientCredentials.load_from_JSON(credentials_file)
+client = pyhandle.handleclient.PyHandleClient('rest').instantiate_with_credentials(
+        creds, HTTPS_verify=https_verify)
+```
+
 # Building the documentation
 
 For more details about the library you can build the documention using [Sphinx](http://www.sphinx-doc.org), requiring at least version 1.3. Sphinx and can be installed via pip. To build HTML documentation locally, then run:
@@ -43,7 +59,6 @@ Check out the documentation [here](https://eudat-b2safe.github.io/PYHANDLE/).
 
 (You can find the source in the GitHub repository at [/docs/source/index.rst](./docs/source/index.rst)!)
 
-
 # License
 
 Copyright 2015-2021, Deutsches Klimarechenzentrum GmbH, GRNET S.A., SURFsara
@@ -141,6 +156,7 @@ source venv/bin/activate
 pip install pypandoc
 python setup.py sdist upload -r pypi
 
+
 # TODO: Deprecated, move to twine!
 ```
 
diff --git a/docs/Dockerfile b/docs/Dockerfile
index b63b898..2b3cc64 100644
--- a/docs/Dockerfile
+++ b/docs/Dockerfile
@@ -1,12 +1,12 @@
 # vim:set ft=dockerfile:
-FROM       eudat-pyhandle
+FROM       python:3.10
+
 
 RUN        apt-get update && apt-get install -y --no-install-recommends \
            make \
         && apt-get clean \
         && rm -rf /var/lib/apt/lists/*
 
-RUN        easy_install pip
 RUN        pip install \
            sphinx
 
diff --git a/docs/Makefile b/docs/Makefile
index 480001b..1a97070 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -3,7 +3,7 @@
 
 # You can set these variables from the command line.
 SPHINXOPTS    =
-SPHINXBUILD   = python -msphinx
+SPHINXBUILD   = python3 -msphinx
 SPHINXPROJ    = PYHANDLE
 SOURCEDIR     = source
 BUILDDIR      = build
@@ -17,4 +17,4 @@ help:
 # Catch-all target: route all unknown targets to Sphinx using the new
 # "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
 %: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/source/creatingclientcertificates.rst b/docs/source/creatingclientcertificates.rst
index 819471b..70ade09 100644
--- a/docs/source/creatingclientcertificates.rst
+++ b/docs/source/creatingclientcertificates.rst
@@ -4,7 +4,7 @@
 Creating the client certificate
 ===============================
 
-For authentication using client certificates, a special pair of keys and a certificate file is required.
+For authentication using client certificates, there is a special pair of keys and a certificate file that it is required.
 Follow these five steps to create them for your users:
 
 1. :ref:`step1`.
@@ -149,4 +149,4 @@ The paths to these files should be entered into the JSON credentials file asfoll
     "certificate_only": "301_certificate_only.pem"
   }
 
-Please follow the client documentation to see how a user can use this JSON file to authenticate while using the b2handle library.
\ No newline at end of file
+Please follow the client documentation to see how a user can use this JSON file to authenticate while using the b2handle library.
diff --git a/docs/source/givingpermissiontousers.rst b/docs/source/givingpermissiontousers.rst
index 210c2ee..badc7be 100644
--- a/docs/source/givingpermissiontousers.rst
+++ b/docs/source/givingpermissiontousers.rst
@@ -5,7 +5,7 @@ Giving write permissions to users
 =================================
 
 No matter which of the methods for *authentication* is used, in both cases the
-Handle Server admin (or prefix owner) has to give write permissions to
+Handle Server admin (or prefix owner) has to give the correct write permissions to
 the user (*authorisation*):
 
 * Authentication: Are you who you claim to be?
diff --git a/docs/source/pyhandleclient.rst b/docs/source/pyhandleclient.rst
index ee0c25d..191bc1a 100644
--- a/docs/source/pyhandleclient.rst
+++ b/docs/source/pyhandleclient.rst
@@ -3,6 +3,8 @@ PyHandleClient
 ==============
 
 
+This is the main class of the PYHANDLE Library. 
+From Here you may understand the PyHandle library and the method it provides.
 PyHandleClient is the main class of the PyHandle library and provides methods to:
 
  * select a client (REST, DB or Batch)
diff --git a/docs/source/pyhandleclientrest.rst b/docs/source/pyhandleclientrest.rst
index 6dba6d7..c9c15ad 100644
--- a/docs/source/pyhandleclientrest.rst
+++ b/docs/source/pyhandleclientrest.rst
@@ -183,6 +183,10 @@ Handle record methods
 
 .. automethod:: pyhandle.handleclient.RESTHandleClient.register_handle
 
+.. automethod:: pyhandle.handleclient.RESTHandleClient.register_handle_kv
+
+.. automethod:: pyhandle.handleclient.RESTHandleClient.register_handle_json
+
 .. automethod:: pyhandle.handleclient.RESTHandleClient.generate_and_register_handle
 
 .. automethod:: pyhandle.handleclient.RESTHandleClient.delete_handle
diff --git a/pyhandle/__init__.py b/pyhandle/__init__.py
index fd686d9..d5f96d6 100644
--- a/pyhandle/__init__.py
+++ b/pyhandle/__init__.py
@@ -1,8 +1,4 @@
-__version__ = "1.0.5-dev"
-# Version 1.0.4 of 2021-02-04 including some pull requests from 2020.
-# The version number had been made 1.0.4 already in 2020, but
-# no release was done back then.
-
+__version__ = "1.1.0"
 # The version as used in setup.py and docs/source/conf.py.
 
 # IMPORTANT
diff --git a/pyhandle/client/dbhandleclient.py b/pyhandle/client/dbhandleclient.py
index 5a2fe4e..88c9594 100644
--- a/pyhandle/client/dbhandleclient.py
+++ b/pyhandle/client/dbhandleclient.py
@@ -294,7 +294,7 @@ def check_if_handle_exists(self, handle):
         query_result_as_dict = query_result_as_dict['count(*)']
         LOGGER.debug('Query result %s', query_result_as_dict)
 
-        if query_result_as_dict is not 0:
+        if query_result_as_dict != 0:
             return True
         else:
             return False
diff --git a/pyhandle/client/resthandleclient.py b/pyhandle/client/resthandleclient.py
index d4145ca..2cd7210 100644
--- a/pyhandle/client/resthandleclient.py
+++ b/pyhandle/client/resthandleclient.py
@@ -129,28 +129,28 @@ def __store_args_or_set_to_defaults(self, args, defaults):
 
         if 'HS_ADMIN_permissions' in args.keys():
             self.__HS_ADMIN_permissions = args['HS_ADMIN_permissions']
-            LOGGER.info(' - HS_ADMIN_permissions set to: ' + self.__HS_ADMIN_permissions)
+            LOGGER.debug(' - HS_ADMIN_permissions set to: ' + self.__HS_ADMIN_permissions)
         else:
             self.__HS_ADMIN_permissions = defaults['HS_ADMIN_permissions']
-            LOGGER.info(' - HS_ADMIN_permissions set to default: ' + self.__HS_ADMIN_permissions)
+            LOGGER.debug(' - HS_ADMIN_permissions set to default: ' + self.__HS_ADMIN_permissions)
 
         
         if 'modify_HS_ADMIN' in args.keys():
             self.__modify_HS_ADMIN = args['modify_HS_ADMIN']
-            LOGGER.info(' - modify_HS_ADMIN set to: ' + str(self.__modify_HS_ADMIN))
+            LOGGER.debug(' - modify_HS_ADMIN set to: ' + str(self.__modify_HS_ADMIN))
         else:
             self.__modify_HS_ADMIN = defaults['modify_HS_ADMIN']
-            LOGGER.info(' - modify_HS_ADMIN set to default: ' + str(self.__modify_HS_ADMIN))
+            LOGGER.debug(' - modify_HS_ADMIN set to default: ' + str(self.__modify_HS_ADMIN))
 
 
         # Handle owner: The user name to be written into HS_ADMIN.
         # Can be specified in json credentials file (optionally):
         if ('handleowner' in args.keys()) and (args['handleowner'] is not None):
             self.__handleowner = args['handleowner']
-            LOGGER.info(' - handleowner set to: ' + self.__handleowner)
+            LOGGER.debug(' - handleowner set to: ' + self.__handleowner)
         else:
             self.__handleowner = None
-            LOGGER.info(' - handleowner: Will be set to default for each created handle separately.')
+            LOGGER.debug(' - handleowner: Will be set to default for each created handle separately.')
 
     @staticmethod
     def instantiate_for_read_access(handle_server_url=None, **config):
@@ -160,7 +160,7 @@ def instantiate_for_read_access(handle_server_url=None, **config):
 
         :param handle_server_url: Optional. The URL of the Handle System
             server to read from. Defaults to 'https://hdl.handle.net'
-        :param \**config: More key-value pairs may be passed that will be passed
+        :param **config: More key-value pairs may be passed that will be passed
             on to the constructor as config. Config options from the
             credentials object are overwritten by this.
         :return: An instance of the client.
@@ -180,7 +180,7 @@ def instantiate_for_read_and_search(handle_server_url, reverselookup_username, r
             reverse lookup servlet.
         :param reverselookup_password: The password to authenticate at the
             reverse lookup servlet.
-        :param \**config: More key-value pairs may be passed that will be passed
+        :param **config: More key-value pairs may be passed that will be passed
             on to the constructor as config. Config options from the
             credentials object are overwritten by this.
         :return: An instance of the client.
@@ -212,7 +212,7 @@ def instantiate_with_username_and_password(handle_server_url, username, password
             "index:prefix/suffix".
         :param password: This is the password stored as secret key in the
             actual Handle value the username points to.
-        :param \**config: More key-value pairs may be passed that will be passed
+        :param **config: More key-value pairs may be passed that will be passed
             on to the constructor as config.
         :raises: :exc:`~pyhandle.handleexceptions.HandleNotFoundException`: If the username handle is not found.
         :raises: :exc:`~pyhandle.handleexceptions.HandleSyntaxError`
@@ -230,7 +230,7 @@ def instantiate_with_credentials(credentials, **config):
 
         :param credentials: A credentials object, see separate class
             PIDClientCredentials.
-        :param \**config: More key-value pairs may be passed that will be passed
+        :param **config: More key-value pairs may be passed that will be passed
             on to the constructor as config. Config options from the
             credentials object are overwritten by this.
         :raises: :exc:`~pyhandle.handleexceptions.HandleNotFoundException`: If the username handle is not found.
@@ -246,7 +246,7 @@ def instantiate_with_credentials(credentials, **config):
 
     # Methods with read access to Handle Server:
 
-    def retrieve_handle_record_json(self, handle):
+    def retrieve_handle_record_json(self, handle, auth=False, indices=None, **hs_options):
         '''
         Retrieve a handle record from the Handle server as a complete nested
         dict (including index, ttl, timestamp, ...) for later use.
@@ -255,6 +255,18 @@ def retrieve_handle_record_json(self, handle):
         please use :meth:`~pyhandle.handleclient.RESTHandleClient.retrieve_handle_record`.
 
         :param handle: The Handle whose record to retrieve.
+        :param auth: Optional. If set to True, the handle record will be retrieved
+            from the primary server and not from cache, so changes from the last
+            max. 24 hours will be included. Defaults to False.
+        :param indices: Optional. A list of indices to retrieve. Defaults to
+            None (i.e. the entire handle record is retrieved). The list can contain
+            integers or strings.
+        :param hs_options: Optional. A list of key-value pairs which will be appended
+            to the URL as parameters, to be passed to the Handle Server during the
+            GET request (e.g. "&type=xyz"). Please see the Handle Tech Manual for
+            possible values. To add several "?index=xyz" options, pass a list or 
+            use the parameter "indices". To add several "?type=xyz" options, add
+            them as a list.
         :raises: :exc:`~pyhandle.handleexceptions.HandleSyntaxError`
         :return: The handle record as a nested dict. If the handle does not
             exist, returns None.
@@ -262,7 +274,16 @@ def retrieve_handle_record_json(self, handle):
         LOGGER.debug('retrieve_handle_record_json...')
 
         utilhandle.check_handle_syntax(handle)
-        response = self.__send_handle_get_request(handle)
+
+        # Add url parameters (see Tech Manual)
+        if auth == True:
+            hs_options['auth'] = 'true'
+        
+        if len(hs_options)>0:
+            response = self.__send_handle_get_request(handle, indices, **hs_options)
+        else:
+            response = self.__send_handle_get_request(handle, indices)
+        
         response_content = decoded_response(response)
                     
         if hsresponses.handle_not_found(response):
@@ -270,7 +291,8 @@ def retrieve_handle_record_json(self, handle):
         elif hsresponses.does_handle_exist(response):
          
             handlerecord_json = json.loads(response_content)
-            if not handlerecord_json['handle'] == handle:
+
+            if not handlerecord_json['handle'] == handle.lstrip('hdl:').lstrip('doi:'):
                 raise GenericHandleError(
                     operation='retrieving handle record',
                     handle=handle,
@@ -288,8 +310,7 @@ def retrieve_handle_record_json(self, handle):
                 response=response
             )
            
-        
-    def retrieve_handle_record(self, handle, handlerecord_json=None):
+    def retrieve_handle_record(self, handle, handlerecord_json=None, auth=False, indices=None, **hs_options):
         '''
         Retrieve a handle record from the Handle server as a dict. If there
         is several entries of the same type, only the first one is
@@ -299,6 +320,18 @@ def retrieve_handle_record(self, handle, handlerecord_json=None):
         :param handle: The handle whose record to retrieve.
         :param handlerecord_json: Optional. If the handlerecord has already
             been retrieved from the server, it can be reused.
+        :param auth: Optional. If set to True, the handle record will be retrieved
+            from the primary server and not from cache, so changes from the last
+            max. 24 hours or so will be included. Defaults to False.
+        :param indices: Optional. A list of indices to retrieve. Defaults to
+            None (i.e. the entire handle is retrieved.). The list can contain
+            integers or strings.
+        :param hs_options: Optional. A list of key-value pairs which will be appended
+            to the URL as parameters, to be passed to the Handle Server during the
+            GET request (e.g. "&type=xyz"). Please see the Handle Tech Manual for
+            possible values. To add several "?index=xyz" options, pass a list or 
+            use the parameter "indices". To add several "?type=xyz" options, add
+            them as a list.
         :return: A dict where the keys are keys from the Handle record (except
             for hidden entries) and every value is a string. The result will be
             None if the Handle does not exist.
@@ -306,7 +339,7 @@ def retrieve_handle_record(self, handle, handlerecord_json=None):
         '''
         LOGGER.debug('retrieve_handle_record...')
 
-        handlerecord_json = self.__get_handle_record_if_necessary(handle, handlerecord_json)
+        handlerecord_json = self.__get_handle_record_if_necessary(handle, handlerecord_json, auth, indices, **hs_options)
         if handlerecord_json is None:
             return None  # Instead of HandleNotFoundException!
         list_of_entries = handlerecord_json['values']
@@ -318,7 +351,7 @@ def retrieve_handle_record(self, handle, handlerecord_json=None):
                 record_as_dict[key] = str(entry['data']['value'])
         return record_as_dict
 
-    def get_value_from_handle(self, handle, key, handlerecord_json=None):
+    def get_value_from_handle(self, handle, key, handlerecord_json=None, auth=False, indices=None, **hs_options):
         '''
         Retrieve a single value from a single Handle. If several entries with
         this key exist, the methods returns the first one. If the handle
@@ -326,6 +359,18 @@ def get_value_from_handle(self, handle, key, handlerecord_json=None):
 
         :param handle: The handle to take the value from.
         :param key: The key.
+        :param auth: Optional. If set to True, the handle record will be retrieved
+            from the primary server and not from cache, so changes from the last
+            max. 24 hours or so will be included. Defaults to False.
+        :param indices: Optional. A list of indices to retrieve. Defaults to
+            None (i.e. the entire handle is retrieved.). The list can contain
+            integers or strings.
+        :param hs_options: Optional. A list of key-value pairs which will be appended
+            to the URL as parameters, to be passed to the Handle Server during the
+            GET request (e.g. "&type=xyz"). Please see the Handle Tech Manual for
+            possible values. To add several "?index=xyz" options, pass a list or 
+            use the parameter "indices". To add several "?type=xyz" options, add
+            them as a list.
         :return: A string containing the value or None if the Handle record
          does not contain the key.
         :raises: :exc:`~pyhandle.handleexceptions.HandleSyntaxError`
@@ -333,12 +378,15 @@ def get_value_from_handle(self, handle, key, handlerecord_json=None):
         '''
         LOGGER.debug('get_value_from_handle...')
 
-        handlerecord_json = self.__get_handle_record_if_necessary(handle, handlerecord_json)
+        handlerecord_json = self.__get_handle_record_if_necessary(handle, handlerecord_json, auth, indices, **hs_options)
         if handlerecord_json is None:
             raise HandleNotFoundException(handle=handle)
         list_of_entries = handlerecord_json['values']
 
-        indices = []
+        # Instead of this filtering, we could simply pass "?type=key" to the Handle Server!!
+        # TODO: Reimplement!
+
+        indices = [] # Why indices? Why not just grab the value!
         for i in xrange(len(list_of_entries)):
             if list_of_entries[i]['type'] == key:
                 indices.append(i)
@@ -355,10 +403,13 @@ def get_value_from_handle(self, handle, key, handlerecord_json=None):
     
     # Methods with write access to Handle Server:
 
-    def generate_and_register_handle(self, prefix, location, checksum=None, **extratypes):
+    def generate_and_register_handle(self, prefix, location, checksum=None, overwrite=False, **extratypes):
         '''
         Register a new Handle with a unique random name (random UUID).
 
+        Note: is a similar legacy method. Instead, just use 
+        generate_PID_name(prefix) to create a handle name and use one of the above.
+
         :param prefix: The prefix of the handle to be registered. The method
             will generate a suffix.
         :param location: The URL of the data entity to be referenced.
@@ -371,6 +422,10 @@ def generate_and_register_handle(self, prefix, location, checksum=None, **extrat
 
         LOGGER.debug('generate_and_register_handle...')
 
+        if 'auth' in extratypes:
+            LOGGER.debug('Found keyword "auth", which will be registered as a key-value-pair in the handle record.')
+            # TODO: Is this behaviour desired?
+
         handle = self.generate_PID_name(prefix)
 
         if not location is None:
@@ -381,11 +436,30 @@ def generate_and_register_handle(self, prefix, location, checksum=None, **extrat
 
         handle = self.register_handle_kv(
             handle,
-            overwrite=True,
+            overwrite,
             **extratypes
         )
         return handle
 
+
+    def modify_or_add_handle_value(self, handle, ttl=None, **kvpairs):
+        add_if_not_exist = True
+        overwrite = True
+        return self.__handle_modification(handle, ttl, add_if_not_exist, overwrite, **kvpairs)
+
+
+    def modify_handle_value_not_add(self, handle, ttl=None, **kvpairs):
+        add_if_not_exist = False
+        overwrite = True
+        return self.__handle_modification(handle, ttl, add_if_not_exist, overwrite, **kvpairs)
+
+
+    def add_handle_value(self, handle, ttl=None, **kvpairs):
+        add_if_not_exist = True
+        overwrite = False
+        return self.__handle_modification(handle, ttl, add_if_not_exist, overwrite, **kvpairs)
+
+
     def modify_handle_value(self, handle, ttl=None, add_if_not_exist=True, **kvpairs):
         '''
         Modify entries (key-value-pairs) in a handle record. If the key
@@ -397,6 +471,8 @@ def modify_handle_value(self, handle, ttl=None, add_if_not_exist=True, **kvpairs
         :param handle: Handle whose record is to be modified
         :param ttl: Optional. Integer value. If ttl should be set to a
             non-default value.
+        :param add_if_not_exist: Optional. Whether a kv pair should be added if
+            the key does not exist yet.
         :param all other args: The user can specify several key-value-pairs.
             These will be the handle value types and values that will be
             modified. The keys are the names or the handle value types (e.g.
@@ -404,18 +480,27 @@ def modify_handle_value(self, handle, ttl=None, add_if_not_exist=True, **kvpairs
             key is 'HS_ADMIN', the new value needs to be of the form
             {'handle':'xyz', 'index':xyz}. The permissions will be set to the
             default permissions.
+        :return: The modified handle.
         :raises: :exc:`~pyhandle.handleexceptions.HandleAuthenticationError`
         :raises: :exc:`~pyhandle.handleexceptions.HandleNotFoundException`
         :raises: :exc:`~pyhandle.handleexceptions.HandleSyntaxError`
         '''
         LOGGER.debug('modify_handle_value...')
+        overwrite = True
+        return self.__handle_modification(handle, ttl, add_if_not_exist, overwrite, **kvpairs)
+
 
-        # Read handle record:
-        handlerecord_json = self.retrieve_handle_record_json(handle)
+    def __handle_modification(self, handle, ttl=None, add_if_not_exist=True, overwrite=True, **kvpairs):
+
+        # Read handle record (the primary one with auth=True,
+        # because we'll modify the primary one!)
+        # But we're talking to the primary anyway, as we're in read-write mode.
+        auth = True # makes no difference!
+        handlerecord_json = self.retrieve_handle_record_json(handle, auth)
         if handlerecord_json is None:
             msg = 'Cannot modify unexisting handle'
             raise HandleNotFoundException(handle=handle, msg=msg)
-        list_of_entries = handlerecord_json['values']
+        list_of_existing_entries = handlerecord_json['values']
 
         # HS_ADMIN
         if 'HS_ADMIN' in kvpairs.keys() and not self.__modify_HS_ADMIN:
@@ -426,38 +511,50 @@ def modify_handle_value(self, handle, ttl=None, add_if_not_exist=True, **kvpairs
                 handle=handle
             )
 
-        nothingchanged = True
+
+        # All the new entries will be in this list, which will be sent to
+        # the Handle Server as payload.
         new_list_of_entries = []
-        list_of_old_and_new_entries = list_of_entries[:]
-        keys = kvpairs.keys()
+
+        # Existing and new/modified entries will be in this list, which is
+        # used/needed for making up new indexes for new entries.
+        # I guess we don't just use the existing list because we iterate through it.
+        list_of_old_and_new_entries = list_of_existing_entries[:]
+
+        # Iterate over all kv pairs that are to be modified/added:
+        nothingchanged = True
         for key, newval in kvpairs.items():
-            # Change existing entry:
+            
+            # Check if that key already exists in the record:
             changed = False
-            for i in xrange(len(list_of_entries)):
-                if list_of_entries[i]['type'] == key:
+            for i in xrange(len(list_of_existing_entries)):
+                current_entry = list_of_existing_entries[i]
+                if current_entry['type'] == key:
+
+                    # If it does, modify it:
                     if not changed:
-                        list_of_entries[i]['data'] = newval
-                        list_of_entries[i].pop('timestamp')  # will be ignored anyway
+                        current_entry['data'] = newval
+                        current_entry.pop('timestamp')  # will be ignored anyway
                         if key == 'HS_ADMIN':
                             newval['permissions'] = self.__HS_ADMIN_permissions
-                            list_of_entries[i].pop('timestamp')  # will be ignored anyway
-                            list_of_entries[i]['data'] = {
+                            current_entry.pop('timestamp')  # will be ignored anyway
+                            current_entry['data'] = {
                                 'format':'admin',
                                 'value':newval
                             }
-                            LOGGER.info('Modified' + \
+                            LOGGER.debug('Modified' + \
                                 ' "HS_ADMIN" of handle ' + handle)
                         changed = True
                         nothingchanged = False
-                        new_list_of_entries.append(list_of_entries[i])
-                        list_of_old_and_new_entries.append(list_of_entries[i])
+                        new_list_of_entries.append(current_entry)
+                        list_of_old_and_new_entries.append(current_entry)
                     else:
                         msg = 'There is several entries of type "' + key + '".' + \
                             ' This can lead to unexpected behaviour.' + \
                             ' Please clean up before modifying the record.'
                         raise BrokenHandleRecordException(handle=handle, msg=msg)
 
-            # If the entry doesn't exist yet, add it:
+            # If the entry doesn't exist yet, add it (if you're allowed to!).
             if not changed:
                 if add_if_not_exist:
                     LOGGER.debug('modify_handle_value: Adding entry "' + key + '"' + \
@@ -468,6 +565,9 @@ def modify_handle_value(self, handle, ttl=None, add_if_not_exist=True, **kvpairs
                     list_of_old_and_new_entries.append(entry_to_add)
                     changed = True
                     nothingchanged = False
+                else:
+                    LOGGER.debug('modify_handle_value: Key "'+key+'" does not exist,' + \
+                        ' but we\'re not allowed to add it to handle "'+handle+'".')
 
         # Add the indices
         indices = []
@@ -477,7 +577,7 @@ def modify_handle_value(self, handle, ttl=None, add_if_not_exist=True, **kvpairs
         # append to the old record:
         if nothingchanged:
             LOGGER.debug('modify_handle_value: There was no entries ' + \
-                str(kvpairs.keys()) + ' to be modified (handle ' + handle + ').' + \
+                str(kvpairs.keys()) + ' to be modified (handle "' + handle + '").' + \
                 ' To add them, set add_if_not_exist = True')
         else:
             op = 'modifying handle values'
@@ -485,7 +585,7 @@ def modify_handle_value(self, handle, ttl=None, add_if_not_exist=True, **kvpairs
                 handle,
                 new_list_of_entries,
                 indices=indices,
-                overwrite=True,
+                overwrite=overwrite,
                 op=op)
             if hsresponses.handle_success(resp):
                 LOGGER.info('Handle modified: ' + handle)
@@ -498,6 +598,8 @@ def modify_handle_value(self, handle, ttl=None, add_if_not_exist=True, **kvpairs
                     msg=msg,
                     payload=put_payload
                 )
+                
+        return json.loads(decoded_response(resp))['handle']
 
     def delete_handle_value(self, handle, key):
         '''
@@ -506,14 +608,18 @@ def delete_handle_value(self, handle, key):
 
         :param handle: Handle from whose record the entry should be deleted.
         :param key: Key to be deleted. Also accepts a list of keys.
+        :return: The deleted handle.
         :raises: :exc:`~pyhandle.handleexceptions.HandleAuthenticationError`
         :raises: :exc:`~pyhandle.handleexceptions.HandleNotFoundException`
         :raises: :exc:`~pyhandle.handleexceptions.HandleSyntaxError`
         '''
         LOGGER.debug('delete_handle_value...')
 
-        # read handle record:
-        handlerecord_json = self.retrieve_handle_record_json(handle)
+        # Read handle record (the primary one with auth=True,
+        # because we'll modify the primary one!)
+        # But we're talking to the primary anyway, as we're in read-write mode.
+        auth = True # makes no difference!
+        handlerecord_json = self.retrieve_handle_record_json(handle, auth)
         if handlerecord_json is None:
             msg = 'Cannot modify unexisting handle'
             raise HandleNotFoundException(handle=handle, msg=msg)
@@ -549,8 +655,11 @@ def delete_handle_value(self, handle, key):
             # delete and process response:
             op = 'deleting "' + str(keys) + '"'
             resp = self.__send_handle_delete_request(handle, indices=indices, op=op)
+
             if hsresponses.handle_success(resp):
                 LOGGER.debug("delete_handle_value: Deleted handle values " + str(keys) + "of handle " + handle)
+                return json.loads(decoded_response(resp))['handle']
+
             elif hsresponses.values_not_found(resp):
                 pass
             else:
@@ -566,6 +675,7 @@ def delete_handle(self, handle, *other):
         :param handle: Handle to be deleted.
         :param other: Deprecated. This only exists to catch wrong method usage
             by users who are used to delete handle VALUES with the method.
+        :return: The deleted handle.
         :raises: :exc:`~pyhandle.handleexceptions.HandleAuthenticationError`
         :raises: :exc:`~pyhandle.handleexceptions.HandleNotFoundException`
         :raises: :exc:`~pyhandle.handleexceptions.HandleSyntaxError`
@@ -586,9 +696,14 @@ def delete_handle(self, handle, *other):
 
         op = 'deleting handle'
         resp = self.__send_handle_delete_request(handle, op=op)
+        handle = json.loads(decoded_response(resp))['handle']
+
         if hsresponses.handle_success(resp):
+            # Response: {'handle': '21.14106/TESTTESTTEST', 'responseCode': 1}   with HTTP 200
             LOGGER.info('Handle ' + handle + ' deleted.')
+            return handle
         elif hsresponses.handle_not_found(resp):
+            # Response: {'handle': '21.14106/TESTTESTTEST', 'responseCode': 100} with HTTP 404
             msg = ('delete_handle: Handle ' + handle + ' did not exist, '
                    'so it could not be deleted.')
             LOGGER.debug(msg)
@@ -596,18 +711,41 @@ def delete_handle(self, handle, *other):
         else:
             raise GenericHandleError(op=op, handle=handle, response=resp)
 
-
     def register_handle_json(self, handle, list_of_entries, overwrite=False):
         '''
-        entry = {'index':index, 'type':entrytype, 'data':data}
-        # Optional 'ttl'
+        Registers a new Handle with given name. If the handle already exists
+        and overwrite is not set to True, the method will throw an
+        exception.
+
+        Note:It allows to pass JSON snippets instead of key-value pairs, so you can 
+        specify the indices. An entry looks like this: 
+        {'index':index, 'type':entrytype, 'data':data}. 
+        This is the format in which the changes are communicated to the handle 
+        server via its REST interface. 
+        An entry of type HS_ADMIN will be added if you do not provide one.
+
+        :param handle: The full name of the handle to be registered (prefix
+            and suffix)
+        :param list_of_entries: The entries to be included in the record,
+            e.g. URL, CHECKSUM, ... Example for an entry:
+            {'index':index, 'type':entrytype, 'data':data}
+            Optionally you can add 'ttl'.
+        :param overwrite: Optional. If set to True, an existing handle record
+            will be overwritten. Defaults to False.
+        :raises: :exc:`~pyhandle.handleexceptions.HandleAlreadyExistsException` Only if overwrite is not set or
+            set to False.
+        :raises: :exc:`~pyhandle.handleexceptions.HandleAuthenticationError`
+        :raises: :exc:`~pyhandle.handleexceptions.HandleSyntaxError`
+        :return: The handle name.
         '''
 
         # If already exists and can't be overwritten:
         if overwrite == False:
             handlerecord_json = self.retrieve_handle_record_json(handle)
+            # Note: Adding "?auth=true" to this request makes no sense, as we are
+            # talking to the primary server anyway.
             if handlerecord_json is not None:
-                msg = 'Could not register handle'
+                msg = 'Could not register handle %s' % handle
                 LOGGER.error(msg + ', as it already exists.')
                 raise HandleAlreadyExistsException(handle=handle, msg=msg)
 
@@ -640,6 +778,12 @@ def register_handle(self, handle, location, checksum=None, additional_URLs=None,
         legacy reasons, as this library was created to replace an earlier
         library that had a method with specifically this signature.
 
+        Note 2: It allows to pass (additionally to the handle name) a 
+        mandatory URL, and optionally a CHECKSUM, and more types as 
+        key-value pairs. Old method, made for legacy reasons, as this library 
+        was created to replace an earlier library that had a method with 
+        specifically this signature.
+
         :param handle: The full name of the handle to be registered (prefix
             and suffix)
         :param location: The URL of the data entity to be referenced
@@ -657,6 +801,10 @@ def register_handle(self, handle, location, checksum=None, additional_URLs=None,
         :return: The handle name.
         '''
 
+        if 'auth' in extratypes:
+            LOGGER.debug('Found keyword "auth", which will be registered as a key-value-pair in the handle record.')
+            # TODO: Is this behaviour desired?
+
         if extratypes is None:
             extratypes = {}
 
@@ -683,8 +831,8 @@ def register_handle_kv(self, handle, overwrite=False, **kv_pairs):
 
         :param handle: The full name of the handle to be registered (prefix
             and suffix)
-        :param extratypes: Optional, but highly recommended. The key value pairs
-            to be included in the record, e.g. URL, CHECKSUM, ...
+        :param kv_pairs: The key value pairs to be included in the record,
+            e.g. URL, CHECKSUM, ...
         :param overwrite: Optional. If set to True, an existing handle record
             will be overwritten. Defaults to False.
         :raises: :exc:`~pyhandle.handleexceptions.HandleAlreadyExistsException` Only if overwrite is not set or
@@ -695,9 +843,15 @@ def register_handle_kv(self, handle, overwrite=False, **kv_pairs):
         '''
         LOGGER.debug('register_handle_kv...')
 
+        if 'auth' in kv_pairs:
+            LOGGER.debug('Found keyword "auth", which will be registered as a key-value-pair in the handle record.')
+            # TODO: Is this behaviour desired?
+
         # If already exists and can't be overwritten:
         if overwrite == False:
             handlerecord_json = self.retrieve_handle_record_json(handle)
+            # Note: Adding "?auth=true" to this request makes no sense, as we are
+            # talking to the primary server anyway.
             if handlerecord_json is not None:
                 msg = 'Could not register handle'
                 LOGGER.error(msg + ', as it already exists.')
@@ -864,8 +1018,10 @@ def __send_handle_delete_request(self, handle, indices=None, op=None):
 
         :param handle: The handle.
         :param indices: Optional. A list of indices to delete. Defaults to
-            None (i.e. the entire handle is deleted.). The list can contain
+            None (i.e. the entire handle record is deleted). The list can contain
             integers or strings.
+        :param op: Name of the operation, e.g. 'registering handle', 'deleting handle'
+            or 'modifying handle values'. Only used in throwing exceptions.
         :return: The server's response.
         '''
 
@@ -885,10 +1041,12 @@ def __send_handle_put_request(self, handle, list_of_entries, indices=None, overw
         :param list_of_entries: A list of handle record entries to be written,
          in the format [{"index":xyz, "type":"xyz", "data":"xyz"}] or similar.
         :param indices: Optional. A list of indices to modify. Defaults
-         to None (i.e. the entire handle is updated.). The list can
+         to None (i.e. the entire handle record is updated). The list can
          contain integers or strings.
         :param overwrite: Optional. Whether the handle should be overwritten
          if it exists already.
+        :param op: Name of the operation, e.g. 'registering handle', 'deleting handle'
+            or 'modifying handle values'. Only used in throwing exceptions.
         :return: The server's response.
         '''
 
@@ -901,33 +1059,58 @@ def __send_handle_put_request(self, handle, list_of_entries, indices=None, overw
         )
         return resp, payload
 
-    def __send_handle_get_request(self, handle, indices=None):
+    def __send_handle_get_request(self, handle, indices=None, **hs_options):
         '''
         Send a HTTP GET request to the handle server to read either an entire
             handle or to some specified values from a handle record, using the
             requests module.
 
         :param handle: The handle.
-        :param indices: Optional. A list of indices to delete. Defaults to
-            None (i.e. the entire handle is deleted.). The list can contain
-            integers or strings.
+        :param indices: Optional. A list of indices to retrieve. Defaults to
+            None (i.e. the entire handle record is retrieved). The list can contain
+            integers or strings. Deprecated. Please use "index" instead.
+        :param hs_options: Optional. A list of key-value pairs which will be appended
+            to the URL as parameters, to be passed to the Handle Server during the
+            GET request (e.g. "&auth=true"). Please see the Handle Tech Manual for
+            possible values. To add several "?index=xyz" options, pass a list or 
+            use the parameter "indices". To add several "?type=xyz" options, add
+            them as a list.
         :return: The server's response.
         '''
 
-        resp = self.__handlesystemconnector.send_handle_get_request(handle, indices)
+        resp = self.__handlesystemconnector.send_handle_get_request(handle, indices, **hs_options)
         return resp
 
-    def __get_handle_record_if_necessary(self, handle, handlerecord_json):
+    def __get_handle_record_if_necessary(self, handle, handlerecord_json, auth, indices, **hs_options):
         '''
         Returns the handle record if it is None or if its handle is not the
             same as the specified handle.
-
+        :param handle: The handle.
+        :param handlerecord_json: The handle record as JSON. If it exists (and if
+            the contained handle matches the handle passed as param), it is simply
+            returned, to avoid repetitive GET requests. If it is None, the handle
+            record will be requested from the Handle Server and returned.
+        :param auth: If set to True, the handle record will be retrieved from the 
+            primary server and not from cache, so changes from the last max. 24 hours
+            will be included. (The cache is refreshed after max 24h by default, this
+            value may differ, depending on handle record's "ttl" value).
+        :param indices: Optional. A list of indices to retrieve. Defaults to
+            None (i.e. the entire handle record is retrieved). The list can contain
+            integers or strings.
+        :param hs_options: Optional. A list of key-value pairs which will be appended
+            to the URL as parameters, to be passed to the Handle Server during the
+            GET request (e.g. "&type=xyz"). Please see the Handle Tech Manual for
+            possible values. To add several "?index=xyz" options, pass a list or 
+            use the parameter "indices". To add several "?type=xyz" options, add
+            them as a list.
         '''
+
+
         if handlerecord_json is None:
-            handlerecord_json = self.retrieve_handle_record_json(handle)
+            handlerecord_json = self.retrieve_handle_record_json(handle, auth, indices, **hs_options)
         else:
             if handle != handlerecord_json['handle']:
-                handlerecord_json = self.retrieve_handle_record_json(handle)
+                handlerecord_json = self.retrieve_handle_record_json(handle, auth, indices, **hs_options)
         return handlerecord_json
 
     def __make_another_index(self, list_of_entries, url=False, hs_admin=False):
diff --git a/pyhandle/handlesystemconnector.py b/pyhandle/handlesystemconnector.py
index a135ecb..e20ceda 100644
--- a/pyhandle/handlesystemconnector.py
+++ b/pyhandle/handlesystemconnector.py
@@ -94,51 +94,51 @@ def __store_args_or_set_to_defaults(self, args, defaults):
 
         if args['handle_server_url']:
             self.__handle_server_url = args['handle_server_url']
-            LOGGER.info(' - handle_server_url set to '+self.__handle_server_url)
+            LOGGER.debug(' - handle_server_url set to '+self.__handle_server_url)
         else:
             self.__handle_server_url = defaults['handle_server_url']
-            LOGGER.info(' - handle_server_url set to default: '+self.__handle_server_url)
+            LOGGER.debug(' - handle_server_url set to default: '+self.__handle_server_url)
 
 
         if args['REST_API_url_extension']:
             self.__REST_API_url_extension = args['REST_API_url_extension']
-            LOGGER.info(' - url_extension_REST_API set to: '+self.__REST_API_url_extension)
+            LOGGER.debug(' - url_extension_REST_API set to: '+self.__REST_API_url_extension)
         else:
             self.__REST_API_url_extension = defaults['REST_API_url_extension']
-            LOGGER.info(' - url_extension_REST_API set to default: '+self.__REST_API_url_extension)
+            LOGGER.debug(' - url_extension_REST_API set to default: '+self.__REST_API_url_extension)
 
 
         if args['HTTPS_verify'] is not None:
             self.__HTTPS_verify = pyhandle.util.get_valid_https_verify(
                 args['HTTPS_verify']
             )
-            LOGGER.info(' - https_verify set to: '+str(self.__HTTPS_verify))
+            LOGGER.debug(' - https_verify set to: '+str(self.__HTTPS_verify))
         else:
             self.__HTTPS_verify = defaults['HTTPS_verify']
-            LOGGER.info(' - https_verify set to default: '+str(self.__HTTPS_verify))
+            LOGGER.debug(' - https_verify set to default: '+str(self.__HTTPS_verify))
 
 
         # Useful for write:
 
         if args['password']:
             self.__password = args['password']
-            LOGGER.info(' - password set.')
+            LOGGER.debug(' - password set.')
 
         if args['username']:
             self.__username = args['username']
-            LOGGER.info(' - username set to: '+self.__username)
+            LOGGER.debug(' - username set to: '+self.__username)
 
         if args['certificate_only']:
             self.__certificate_only = args['certificate_only']
-            LOGGER.info(' - certificate_only set to: '+str(self.__certificate_only))
+            LOGGER.debug(' - certificate_only set to: '+str(self.__certificate_only))
 
         if args['private_key']:
             self.__private_key = args['private_key']
-            LOGGER.info(' - private_key set to: '+str(self.__private_key))
+            LOGGER.debug(' - private_key set to: '+str(self.__private_key))
 
         if args['certificate_and_key']:
             self.__certificate_and_key = args['certificate_and_key']
-            LOGGER.info(' - certificate_and_key set to: '+str(self.__certificate_and_key))
+            LOGGER.debug(' - certificate_and_key set to: '+str(self.__certificate_and_key))
 
     def __check_if_write_access(self, args):
         write_access_argnames = ['username', 'password', 'certificate_only', 'private_key', 'certificate_and_key']
@@ -170,6 +170,11 @@ def __setup_for_writeaccess(self, args):
             msg = 'Unknown authentication method: "'+self.__authentication_method+'".'
             self.__has_write_access = False
 
+        if self.__has_write_access:
+            LOGGER.info('Client configured for write access.')
+        else:
+            LOGGER.info('Client could not be configured for write access.')
+
     def __setup_for_auth_by_user_and_pw(self):
 
         # Check username:
@@ -194,7 +199,7 @@ def __setup_for_auth_by_clientcertificate(self):
                 msg += 'The certificate file was not found at the specified path: '+self.__certificate_only
             if not os.path.isfile(self.__private_key):
                 msg += 'The private key file was not found at the specified path: '+self.__private_key
-            if msg is not '':
+            if msg != '':
                 raise CredentialsFormatError(msg=msg)
 
     def __which_authentication_method(self):
@@ -248,22 +253,25 @@ def __set_basic_auth_string(self, username, password):
 
     # API methods:
 
-    def send_handle_get_request(self, handle, indices=None):
+    def send_handle_get_request(self, handle, indices=None, **hs_options):
         '''
         Send a HTTP GET request to the handle server to read either an entire
             handle or to some specified values from a handle record, using the
             requests module.
 
         :param handle: The handle.
-        :param indices: Optional. A list of indices to delete. Defaults to
-            None (i.e. the entire handle is deleted.). The list can contain
+        :param indices: Optional. A list of indices to retrieve. Defaults to
+            None (i.e. the entire handle record is retrieved). The list can contain
             integers or strings.
+        :param hs_options: Optional. A list of key-value pairs which will be appended
+            to the URL as parameters, to be passed to the Handle Server during the
+            GET request (e.g. "&auth=true"). Please see the Handle Tech Manual for
+            possible values.
         :return: The server's response.
         '''
 
-
         # Assemble required info:
-        url = self.make_handle_URL(handle, indices)
+        url = self.make_handle_URL(handle, indices, **hs_options)
         LOGGER.debug('GET Request to '+url)
         head = self.__get_headers('GET')
         veri = self.__HTTPS_verify
@@ -296,7 +304,6 @@ def __cert_needed_for_get_request(self):
         else:
             return False
 
-
     def send_handle_put_request(self, **args):
         '''
         Send a HTTP PUT request to the handle server to write either an entire
@@ -495,11 +502,11 @@ def __get_headers(self, action):
         accept = 'application/json'
         content_type = 'application/json'
 
-        if action is 'GET':
+        if action == 'GET':
             header['Accept'] = accept
 
 
-        elif action is 'PUT' or action is 'DELETE':
+        elif action == 'PUT' or action == 'DELETE':
 
             if self.__authentication_method == self.__auth_methods['cert']:
                 header['Authorization'] = 'Handle clientCert="true"'
@@ -507,7 +514,7 @@ def __get_headers(self, action):
             elif self.__authentication_method == self.__auth_methods['user_pw']:
                 header['Authorization'] = 'Basic ' + self.__basic_authentication_string
 
-            if action is 'PUT':
+            if action == 'PUT':
                 header['Content-Type'] = content_type
 
 
@@ -515,7 +522,7 @@ def __get_headers(self, action):
             LOGGER.debug('__getHeader: ACTION is unknown ('+action+')')
         return header
 
-    def make_handle_URL(self, handle, indices=None, overwrite=None, other_url=None):
+    def make_handle_URL(self, handle, indices=None, overwrite=None, other_url=None, **options):
         '''
         Create the URL for a HTTP request (URL + query string) to request
         a specific handle from the Handle Server.
@@ -531,12 +538,24 @@ def make_handle_URL(self, handle, indices=None, overwrite=None, other_url=None):
             one specified in the constructor should be used. Defaults to None.
             If set, it should be set including the URL extension,
             e.g. '/api/handles/'.
+        :param options: Optional. A list of key-value pairs which will be appended
+            to the URL as parameters, to be passed to the Handle Server during the
+            GET request (e.g. "&type=xyz"). Please see the Handle Tech Manual for
+            possible values. To add several "?index=xyz" options, pass a list or 
+            use the parameter "indices". To add several "?type=xyz" options, add
+            them as a list.
         :return: The complete URL, e.g.
          'http://some.handle.server/api/handles/prefix/suffix?index=2&index=6&overwrite=false
         '''
         LOGGER.debug('make_handle_URL...')
         separator = '?'
 
+        # Handle Server does not accept REST API requests with "hdl:" nor "doi:",
+        # it interprets them as part of the prefix.
+        # It will respond with HTTP Status Code 400 and Response: {"responseCode":301,
+        # "message":"That prefix doesn't live here","handle":"hdl:21.14106/TESTTESTTEST"}
+        handle = handle.lstrip('hdl:').lstrip('doi:')
+
         if other_url is not None:
             url = other_url
         else:
@@ -545,17 +564,48 @@ def make_handle_URL(self, handle, indices=None, overwrite=None, other_url=None):
         url = url.strip('/')+'/'+ handle
 
         if indices is None:
-            indices = []
-        if len(indices) > 0:
+            pass
+        elif len(indices) > 0:
             for index in indices:
+                # TODO: It might be possible to replace this by "?index=various", see Tech Manual.
+                # But be careful, "various" only works with PUT, not with DELETE or GET.
                 url = url+separator+'index='+str(index)
                 separator = '&'
 
+        # Index can be passed several times:
+        # Note: This is not required now, as several "index" values can be passed using "indices", but
+        # TODO: I think we should through away "indices", as this is more elegant.
+        if 'index' in options:
+            if type(options['index']) == type([]):
+                for item in options['index']:
+                    url = url+separator+'index='+str(item)
+                    separator = '&'
+                del options['index']
+
+        # Type can be passed several times:
+        if 'type' in options:
+            if type(options['type']) == type([]):
+                for item in options['type']:
+                    url = url+separator+'type='+str(item)
+                    separator = '&'
+                del options['type']
+
         if overwrite is not None:
             if overwrite:
                 url = url+separator+'overwrite=true'
+                separator = '&'
             else:
                 url = url+separator+'overwrite=false'
+                separator = '&'
+
+        if len(options) > 0:
+            for k,v in options.items():
+                if v == True:
+                    v = 'true'
+                elif v == False:
+                    v = 'false'
+                url = url+separator+k+'='+str(v)
+                separator = '&'
 
         return url
 
diff --git a/pyhandle/searcher.py b/pyhandle/searcher.py
index 56fa7e1..ba64f73 100644
--- a/pyhandle/searcher.py
+++ b/pyhandle/searcher.py
@@ -83,9 +83,9 @@ def __setup_search_access(self):
         self.__check_and_set_search_access()
         self.__session = requests.Session()
         if self.__session is None:
-            LOGGER.info('Session could not be created.')
+            LOGGER.debug('Search session could not be created.')
         else:
-            LOGGER.debug('Session was created.')
+            LOGGER.debug('Search session was created.')
 
     def __check_and_set_search_access(self):
         user_and_pw_exist = self.__check_and_set_search_authentication()
@@ -100,17 +100,17 @@ def __check_and_set_search_authentication(self):
         if self.__user is not None and self.__password is not None:
             self.__set_revlookup_auth_string(self.__user, self.__password)
             self.__header = {'Authorization': 'Basic ' + self.__revlookup_auth_string}
-            LOGGER.info('Reverse lookup authentication is set.')
+            LOGGER.debug('Reverse lookup authentication is set.')
             return True
 
         else:
             msg = 'Reverse lookup not possible.'
             if self.__user is None and self.__password is None:
-                LOGGER.info(msg+' Neither username nor password were provided.')
+                LOGGER.debug(msg+' Neither username nor password were provided.')
             elif self.__user is None:
-                LOGGER.info(msg+' Username not provided. Password is '+str(self.__password))
+                LOGGER.debug(debug+' Username not provided. Password is '+str(self.__password))
             else:
-                LOGGER.info(msg+' Password not provided. Username is '+str(self.__user))
+                LOGGER.debug(debug+' Password not provided. Username is '+str(self.__user))
             return False
 
     def __check_and_set_search_url(self):
@@ -122,16 +122,16 @@ def __check_and_set_search_url(self):
                 self.__reverselookup_url_extension.strip('/')
             )
             return True
-            LOGGER.info('Reverse lookup endpoint set to '+str(self.__search_url))
+            LOGGER.debug('Reverse lookup endpoint set to '+str(self.__search_url))
         else:
             msg = 'Reverse lookup not possible.'
             if (self.__reverselookup_baseuri is None and
                 self.__reverselookup_url_extension is None):
-                LOGGER.info(msg+' No URL for reverse lookup provided.')
+                LOGGER.debug(msg+' No URL for reverse lookup provided.')
             elif self.__reverselookup_baseuri is None:
-                LOGGER.info(msg+' No URL for reverse lookup provided.')
+                LOGGER.debug(msg+' No URL for reverse lookup provided.')
             else:
-                LOGGER.info(msg+' No URL path for reverse lookup provided.')
+                LOGGER.debug(msg+' No URL path for reverse lookup provided.')
             return False
 
     def get_search_endpoint(self):
@@ -153,33 +153,33 @@ def __store_args_or_set_to_defaults(self, args, defaults):
             self.__HTTPS_verify = pyhandle.util.get_valid_https_verify(
                 args['HTTPS_verify']
             )
-            LOGGER.info(' - https_verify set to: '+str(self.__HTTPS_verify))
+            LOGGER.debug(' - https_verify set to: '+str(self.__HTTPS_verify))
         else:
             self.__HTTPS_verify = defaults['HTTPS_verify']
-            LOGGER.info(' - https_verify set to default: '+str(self.__HTTPS_verify))
+            LOGGER.debug(' - https_verify set to default: '+str(self.__HTTPS_verify))
 
         if args['allowed_search_keys'] is not None: # Without this check, empty lists are not found!
             self.__allowed_search_keys = args['allowed_search_keys']
-            LOGGER.info(' - allowed_search_keys set to: '+str(self.__allowed_search_keys))
+            LOGGER.debug(' - allowed_search_keys set to: '+str(self.__allowed_search_keys))
         else:
             self.__allowed_search_keys = defaults['allowed_search_keys']
-            LOGGER.info(' - allowed_search_keys set to default: '+str(self.__allowed_search_keys))
+            LOGGER.debug(' - allowed_search_keys set to default: '+str(self.__allowed_search_keys))
 
         if args['reverselookup_baseuri']:
             self.__reverselookup_baseuri = args['reverselookup_baseuri']
-            LOGGER.info(' - solrbaseurl set to: '+self.__reverselookup_baseuri)
+            LOGGER.debug(' - solrbaseurl set to: '+self.__reverselookup_baseuri)
         elif 'handle_server_url' in args.keys() and args['handle_server_url'] is not None:
             self.__reverselookup_baseuri = args['handle_server_url']
-            LOGGER.info(' - solrbaseurl set to same as handle server: '+str(self.__reverselookup_baseuri))
+            LOGGER.debug(' - solrbaseurl set to same as handle server: '+str(self.__reverselookup_baseuri))
         else:
-            LOGGER.info(' - solrbaseurl: No default.')
+            LOGGER.debug(' - solrbaseurl: No default.')
 
         if args['reverselookup_url_extension']:
             self.__reverselookup_url_extension = args['reverselookup_url_extension']
-            LOGGER.info(' - reverselookup_url_extension set to: '+self.__reverselookup_url_extension)
+            LOGGER.debug(' - reverselookup_url_extension set to: '+self.__reverselookup_url_extension)
         else:
             self.__reverselookup_url_extension = defaults['reverselookup_url_extension']
-            LOGGER.info(' - reverselookup_url_extension set to default: '+self.__reverselookup_url_extension)
+            LOGGER.debug(' - reverselookup_url_extension set to default: '+self.__reverselookup_url_extension)
 
         # Authentication reverse lookup:
         #   If specified, use it.
@@ -188,23 +188,23 @@ def __store_args_or_set_to_defaults(self, args, defaults):
 
         if args['reverselookup_username']:
             self.__user = args['reverselookup_username']
-            LOGGER.info(' - reverselookup_username set to: '+self.__user)
+            LOGGER.debug(' - reverselookup_username set to: '+self.__user)
         elif args['username']:
             self.__user = args['username']
             self.__handle_system_username_used = True
-            LOGGER.info(' - reverselookup_username set to handle server username: '+self.__user)
+            LOGGER.debug(' - reverselookup_username set to handle server username: '+self.__user)
         else:
-            LOGGER.info(' - reverselookup_username: Not specified. No default.')
+            LOGGER.debug(' - reverselookup_username: Not specified. No default.')
 
         if args['reverselookup_password']:
             self.__password = args['reverselookup_password']
-            LOGGER.info(' - reverselookup_password set.')
+            LOGGER.debug(' - reverselookup_password set.')
         elif args['password']:
             self.__password = args['password']
             self.__handle_system_password_used = True
-            LOGGER.info(' - reverselookup_password set to handle server password.')
+            LOGGER.debug(' - reverselookup_password set to handle server password.')
         else:
-            LOGGER.info(' - reverselookup_password: Not specified. No default.')
+            LOGGER.debug(' - reverselookup_password: Not specified. No default.')
 
 
     def search_handle(self, **args):
diff --git a/pyhandle/util/logutils.py b/pyhandle/util/logutils.py
index 21a16e4..f47b1dc 100644
--- a/pyhandle/util/logutils.py
+++ b/pyhandle/util/logutils.py
@@ -45,9 +45,9 @@ def log_instantiation(LOGGER, classname, args, forbidden, with_date=False):
 
     # Info:
     if with_date:
-            LOGGER.info('Instantiating '+classname+' at '+datetime.datetime.now().strftime('%Y-%m-%d_%H:%M'))
+            LOGGER.debug('Instantiating '+classname+' at '+datetime.datetime.now().strftime('%Y-%m-%d_%H:%M'))
     else:
-        LOGGER.info('Instantiating '+classname)
+        LOGGER.debug('Instantiating '+classname)
 
     # Debug:
     for argname in args:
diff --git a/pyhandle/utilhandle.py b/pyhandle/utilhandle.py
index dd95cef..bd26b4f 100644
--- a/pyhandle/utilhandle.py
+++ b/pyhandle/utilhandle.py
@@ -11,6 +11,7 @@
 from . import handleexceptions
 from . import util
 
+
 def remove_index_from_handle(handle_with_index):
     '''
     Returns index and handle separately, in a tuple.
@@ -63,7 +64,19 @@ def check_handle_syntax(string):
         raise handleexceptions.HandleSyntaxError(msg=msg, handle=string, expected_syntax=expected)
 
     if ':' in string:
-        check_handle_syntax_with_index(string, base_already_checked=True)
+        if string.startswith('hdl:'): # Fixing https://github.com/EUDAT-B2HANDLE/PYHANDLE/issues/49
+            # TODO: Note: What about DOIs? -> Fixed in a different PR
+            # Note of caution: Handle Server won't accept REST API calls with hdl: prepended.
+            # It will respond with HTTP Status Code 400 and Response: {"responseCode":301,
+            # "message":"That prefix doesn't live here","handle":"hdl:21.14106/TESTTESTTEST"}
+            # The problem is prevented by removing any hdl: or doi: right before making the request.
+            return True
+            
+        else:
+            check_handle_syntax_with_index(string, base_already_checked=True)
+            # TODO: Actually this is not a handle, but refers to a field inside a handle record, so
+            # to be strict, we should not accept this.
+
 
     return True
 
@@ -92,8 +105,12 @@ def check_handle_syntax_with_index(string, base_already_checked=False):
     try:
         int(arr[0])
     except ValueError:
-        msg = 'Index is not an integer'
-        raise handleexceptions.HandleSyntaxError(msg=msg, handle=string, expected_syntax=expected)
+        if arr[0] == 'hdl:':
+            msg = 'Handle string starts with "hdl:", not with an index.'
+            raise handleexceptions.HandleSyntaxError(msg=msg, handle=string, expected_syntax=expected)
+        else:
+            msg = 'Index is not an integer'
+            raise handleexceptions.HandleSyntaxError(msg=msg, handle=string, expected_syntax=expected)
 
     if not base_already_checked:
         check_handle_syntax(string)
diff --git a/setup.py b/setup.py
index 5222058..dfb10c9 100644
--- a/setup.py
+++ b/setup.py
@@ -89,14 +89,15 @@ def find_version(*file_paths):
       packages=find_packages(),
       zip_safe=False,
       install_requires=[
-          'requests',
+          'requests==2.23.0',
           'datetime',
           'future',
           'six',
-          'pymysql',
+          'pymysql==0.8.0 ; python_version < "2.8.0"',
+          'pymysql==0.8.0 ; python_version < "3.11.0"'       
       ],
       tests_require=test_dependencies,
-      python_requires='>=3.6.*<=3.10',
+      python_requires='>=3.6.*<3.11',
       cmdclass={'test': NoseTestCommand},
       include_package_data=True
 )


Test patch:
diff --git a/pyhandle/tests/.coverage b/pyhandle/tests/.coverage
new file mode 100644
index 0000000..153ea01
Binary files /dev/null and b/pyhandle/tests/.coverage differ
diff --git a/pyhandle/tests/.coveragerc b/pyhandle/tests/.coveragerc
new file mode 100644
index 0000000..3afe39f
--- /dev/null
+++ b/pyhandle/tests/.coveragerc
@@ -0,0 +1,13 @@
+[paths]
+source = src
+
+[run]
+branch = true
+source =
+    tests
+parallel = true
+
+[report]
+show_missing = true
+precision = 2
+omit = *migrations*
diff --git a/pyhandle/tests/Dockerfile b/pyhandle/tests/Dockerfile
deleted file mode 100644
index ab76b79..0000000
--- a/pyhandle/tests/Dockerfile
+++ /dev/null
@@ -1,22 +0,0 @@
-# vim:set ft=dockerfile:
-FROM       eudat-pyhandle
-
-RUN        easy_install pip
-RUN        pip install \
-           mock \
-           coverage \
-           nose
-
-VOLUME     /opt/PYHANDLE/pyhandle/tests
-
-WORKDIR    /opt/PYHANDLE/pyhandle/tests
-
-COPY       docker-entrypoint.sh ./
-
-ENTRYPOINT ["./docker-entrypoint.sh"]
-
-CMD        ["coverage"]
-
-
-# TODO: Document build + usage of this Dockerfile.
-
diff --git a/pyhandle/tests/Dockerfile-py3.5 b/pyhandle/tests/Dockerfile-py3.5
deleted file mode 100644
index faac120..0000000
--- a/pyhandle/tests/Dockerfile-py3.5
+++ /dev/null
@@ -1,34 +0,0 @@
-# vim:set ft=dockerfile:
-FROM       eudat-pyhandle:py3.5
-
-# To build base image:
-#cd ../../
-#docker build -t eudat-pyhandle:py3.5 -f Dockerfile-py3.5 .
-
-# To build this:
-#cd pyhandle/tests
-#docker build -t temp:py3.5 -f Dockerfile-py3.5 .
-
-# It builds successfully, but fails to run:
-#docker run -it temp:py3.5
-
-
-RUN        pip3 install \
-           mock \
-           coverage \
-           nose
-
-VOLUME     /opt/PYHANDLE/pyhandle/tests
-
-WORKDIR    /opt/PYHANDLE/pyhandle/tests
-
-COPY       docker-entrypoint.sh ./
-
-ENTRYPOINT ["./docker-entrypoint.sh"]
-
-CMD        ["coverage"]
-
-# TODO: Fix this Dockerfile.
-# TODO: Document usage of this Dockerfile.
-
-
diff --git a/pyhandle/tests/docker-entrypoint-310.sh b/pyhandle/tests/docker-entrypoint-310.sh
new file mode 100755
index 0000000..e7ee995
--- /dev/null
+++ b/pyhandle/tests/docker-entrypoint-310.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+set -e
+
+if [ "$1" = 'coverage' ]; then
+  pytest --cov-report xml:coverage.xml --cov=pyhandle main_test_script.py --cov-config=.coveragerc
+else
+  exec "$@"
+fi
diff --git a/pyhandle/tests/docker-entrypoint.sh b/pyhandle/tests/docker-entrypoint.sh
old mode 100644
new mode 100755
diff --git a/pyhandle/tests/mockresponses.py b/pyhandle/tests/mockresponses.py
index 9cc0c2f..bf1bd0d 100644
--- a/pyhandle/tests/mockresponses.py
+++ b/pyhandle/tests/mockresponses.py
@@ -30,22 +30,25 @@ class MockResponse(object):
     which has a specific combination of HTTP status code, handle
     response code and content.
     '''
-    def __init__(self, status_code=None, content=None, request=None, success=False, notfound=False, empty=False, wascreated=False):
+    def __init__(self, handle=None, status_code=None, content=None, request=None, success=False, notfound=False, empty=False, wascreated=False):
 
         self.content = None
         self.status_code = None
         self.request = None
 
         # Some predefined cases:
+        if handle is not None and (handle.startswith('hdl:') or handle.startswith('doi:')):
+            self.status_code = 400
         if success:
             self.status_code = 200
             self.content = '{"responseCode":1, "handle":"my/testhandle"}'
         elif notfound:
             self.status_code = 404
-            self.content = '{"responseCode":100}'
+            self.content = '{"responseCode":100, "handle":"my/testhandle"}'
+            # This response to a delete-handle request was verified 2022-06-28 by Merret
         elif empty:
             self.status_code = 200
-            self.content = '{"responseCode":200}'
+            self.content = '{"responseCode":200, "handle":"my/testhandle"}'
         elif wascreated:
             self.status_code = 201
             self.content = '{"responseCode":1, "handle":"my/testhandle"}'
@@ -58,7 +61,7 @@ def __init__(self, status_code=None, content=None, request=None, success=False,
             self.request = request
         # Defaults (they do not override):
         if self.content is None:    
-            self.content = '{"responseCode":1}'
+            self.content = '{"responseCode":1, "handle":"my/testhandle"}'
         if self.status_code is None:
             self.status_code = 200
         if self.request is None:
diff --git a/pyhandle/tests/testcases/handleclient_2_read_patched_unit_test.py b/pyhandle/tests/testcases/handleclient_2_read_patched_unit_test.py
index 0dc3524..be84849 100644
--- a/pyhandle/tests/testcases/handleclient_2_read_patched_unit_test.py
+++ b/pyhandle/tests/testcases/handleclient_2_read_patched_unit_test.py
@@ -75,7 +75,7 @@ def test_retrieve_handle_record_json_handle_empty(self, getpatch):
 
         # Call method and check result:
         json_record = self.inst.retrieve_handle_record_json(testhandle)
-        self.assertEquals(json_record['responseCode'],200,
+        self.assertEqual(json_record['responseCode'],200,
             'Unexpected return value: '+str(json_record))
 
     @mock.patch('pyhandle.handleclient.requests.Session.get')
diff --git a/pyhandle/tests/testcases/handleclient_read_patched_unit_test.py b/pyhandle/tests/testcases/handleclient_read_patched_unit_test.py
index ebef313..6e0f0e4 100644
--- a/pyhandle/tests/testcases/handleclient_read_patched_unit_test.py
+++ b/pyhandle/tests/testcases/handleclient_read_patched_unit_test.py
@@ -39,7 +39,7 @@ def test_get_value_from_handle_normal(self):
         val = self.inst.get_value_from_handle(handle,
                                               'TEST1',
                                               handlerecord)
-        self.assertEquals(val, 'val1',
+        self.assertEqual(val, 'val1',
             'The value of "TEST1" should be "val1".')
 
     def test_get_value_from_handle_inexistentvalue(self):
diff --git a/pyhandle/tests/testcases/handleclient_search_unit_test.py b/pyhandle/tests/testcases/handleclient_search_unit_test.py
index be1bdb1..699005b 100644
--- a/pyhandle/tests/testcases/handleclient_search_unit_test.py
+++ b/pyhandle/tests/testcases/handleclient_search_unit_test.py
@@ -28,32 +28,60 @@ def tearDown(self):
         pass
 
     def test_search_handle_for_forbiddenkeys(self):
-        with self.assertRaisesRegexp(ReverseLookupException, 'Cannot search for key[.]*'):
-            self.inst.search_handle(url='*dkrz*',
+        if (sys.version_info.major == 2):
+            with self.assertRaisesRegexp(ReverseLookupException, 'Cannot search for key[.]*'):
+                self.inst.search_handle(url='*dkrz*',
+                                          checksum='*123*',
+                                          anotherfield='xyz')
+        else:
+            with self.assertRaisesRegex(ReverseLookupException, 'Cannot search for key[.]*'):
+                self.inst.search_handle(url='*dkrz*',
                                           checksum='*123*',
                                           anotherfield='xyz')
-        
     def test_search_handle_for_fulltext(self):
-        with self.assertRaisesRegexp(ReverseLookupException, 'Full-text search is not implemented yet[.]*'):
-            self.inst.search_handle(url='*dkrz*',
+        if (sys.version_info.major == 2):
+            with self.assertRaisesRegexp(ReverseLookupException, 'Full-text search is not implemented yet[.]*'):
+                self.inst.search_handle(url='*dkrz*',
+                                          checksum='*123*',
+                                          searchterms=['searchterm1', 'searchterm2'])
+        else:
+            with self.assertRaisesRegex(ReverseLookupException, 'Full-text search is not implemented yet[.]*'):
+                self.inst.search_handle(url='*dkrz*',
                                           checksum='*123*',
                                           searchterms=['searchterm1', 'searchterm2'])
 
     def test_search_handle_noterms(self):
-        with self.assertRaisesRegexp(ReverseLookupException, 'No search terms have been specified[.]*'):
-            self.inst.search_handle()
+        if (sys.version_info.major == 2):
+            with self.assertRaisesRegexp(ReverseLookupException, 'No search terms have been specified[.]*'):
+                self.inst.search_handle()
+        else:         
+            with self.assertRaisesRegex(ReverseLookupException, 'No search terms have been specified[.]*'):
+                self.inst.search_handle()
 
     def test_create_revlookup_query_fulltext(self):
-        with self.assertRaisesRegexp(ReverseLookupException, 'Full-text search is not implemented yet[.]*'):
-            self.searcher.create_revlookup_query('foo', 'bar')
+        if (sys.version_info.major == 2):
+            with self.assertRaisesRegexp(ReverseLookupException, 'Full-text search is not implemented yet[.]*'):
+                self.searcher.create_revlookup_query('foo', 'bar')
+        else: 
+            with self.assertRaisesRegex(ReverseLookupException, 'Full-text search is not implemented yet[.]*'):
+                self.searcher.create_revlookup_query('foo', 'bar')
 
     def test_create_revlookup_query_forbiddenkeys(self):
-        with self.assertRaisesRegexp(ReverseLookupException, 'Cannot search for key[.]*'):
-            self.searcher.create_revlookup_query(foo='foo', bar='bar')
+        if (sys.version_info.major == 2):
+            with self.assertRaisesRegexp(ReverseLookupException, 'Cannot search for key[.]*'):
+                self.searcher.create_revlookup_query(foo='foo', bar='bar')
+        else: 
+            with self.assertRaisesRegex(ReverseLookupException, 'Cannot search for key[.]*'):
+                self.searcher.create_revlookup_query(foo='foo', bar='bar')
+
 
     def test_create_revlookup_query_noterms(self):
-        with self.assertRaisesRegexp(ReverseLookupException, 'No search terms have been specified[.]*'):
-            self.searcher.create_revlookup_query()
+        if (sys.version_info.major == 2):
+            with self.assertRaisesRegexp(ReverseLookupException, 'No search terms have been specified[.]*'):
+                self.searcher.create_revlookup_query()
+        else:
+            with self.assertRaisesRegex(ReverseLookupException, 'No search terms have been specified[.]*'):
+                self.searcher.create_revlookup_query()
 
     def test_create_revlookup_query_norestriction(self):
         searcher = Searcher(allowed_search_keys=[])
diff --git a/pyhandle/tests/testcases/handleclient_unit_test.py b/pyhandle/tests/testcases/handleclient_unit_test.py
index 9503e4d..7ab6090 100644
--- a/pyhandle/tests/testcases/handleclient_unit_test.py
+++ b/pyhandle/tests/testcases/handleclient_unit_test.py
@@ -175,5 +175,5 @@ def test_retrieve_handle_record_when_handle_is_None(self):
     def test_create_authentication_string(self):
         auth = create_authentication_string('100:user/name', 'password123')
         expected = 'MTAwJTNBdXNlci9uYW1lOnBhc3N3b3JkMTIz'
-        self.assertEquals(expected, auth,
+        self.assertEqual(expected, auth,
                           'Authentication string is: ' + auth + ', but should be: ' + expected)
diff --git a/pyhandle/tests/testcases/handleclient_write_patched_unit_test.py b/pyhandle/tests/testcases/handleclient_write_patched_unit_test.py
index c57d4ed..d5fb232 100644
--- a/pyhandle/tests/testcases/handleclient_write_patched_unit_test.py
+++ b/pyhandle/tests/testcases/handleclient_write_patched_unit_test.py
@@ -36,6 +36,7 @@ class RESTHandleClientWriteaccessPatchedTestCase(unittest.TestCase):
 
     @mock.patch('pyhandle.handlesystemconnector.HandleSystemConnector.check_if_username_exists')
     def setUp(self, username_check_patch):
+        self.maxDiff = None
 
         # Define replacement for the patched check for username existence:
         username_check_patch = mock.Mock()
@@ -94,13 +95,23 @@ def test_register_handle(self, getpatch, putpatch):
         
         # Compare with expected payload:
         #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}, {"index": 5, "type": "10320/LOC", "data": "<locations><location href=\"http://bar.bar\" id=\"0\" /><location href=\"http://foo.foo\" id=\"1\" /></locations>"}]}
-        #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}]}
-        expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}]}
-        replace_timestamps(expected_payload)
-        self.assertIsNotNone(flattensort(passed_payload))
-        self.assertIsNotNone(flattensort(expected_payload))
-        self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
-            failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
+        
+        #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}]}
+        
+        if (sys.version_info.major == 3 and sys.version_info.minor > 5):
+            expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}]}
+            replace_timestamps(expected_payload)
+            self.assertIsNotNone(flattensort(passed_payload))
+            self.assertIsNotNone(flattensort(expected_payload))
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
+        elif (sys.version_info.major == 2 and sys.version_info.minor > 6):
+            expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}]}
+            replace_timestamps(expected_payload)
+            self.assertIsNotNone(flattensort(passed_payload))
+            self.assertIsNotNone(flattensort(expected_payload))
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
     
     @mock.patch('pyhandle.handlesystemconnector.requests.Session.put')
     @mock.patch('pyhandle.handlesystemconnector.requests.Session.get')
@@ -133,13 +144,14 @@ def test_register_handle_kv(self, getpatch, putpatch):
         passed_payload, _ = self.get_payload_headers_from_mockresponse(putpatch)
         
         # Compare with expected payload:
-        expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}]}
-        #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}]}
-        replace_timestamps(expected_payload)
-        self.assertIsNotNone(flattensort(passed_payload))
-        self.assertIsNotNone(flattensort(expected_payload))
-        self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
-            failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
+        if (sys.version_info.major == 3 and sys.version_info.minor != 5):
+            expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}]}
+            #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}]}
+            replace_timestamps(expected_payload)
+            self.assertIsNotNone(flattensort(passed_payload))
+            self.assertIsNotNone(flattensort(expected_payload))
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
     
     @mock.patch('pyhandle.handlesystemconnector.requests.Session.put')
     @mock.patch('pyhandle.handlesystemconnector.requests.Session.get')
@@ -333,14 +345,27 @@ def test_register_handle_different_owner(self, getpatch, putpatch, username_chec
         # Compare with expected payload:
         # Previously contained 10320LOC field:
         #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": 300, "handle": "handle/owner", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}, {"index": 5, "type": "10320/LOC", "data": "<locations><location href=\"http://bar.bar\" id=\"0\" /><location href=\"http://foo.foo\" id=\"1\" /></locations>"}]}
-        # Changed order/index:
-        #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": 300, "handle": "handle/owner", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}]}
-        expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": 300, "handle": "handle/owner", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}]}
-        replace_timestamps(expected_payload)
-        self.assertIsNotNone(flattensort(passed_payload))
-        self.assertIsNotNone(flattensort(expected_payload))
-        self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
-            failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
+        # Changed order/index: old one
+        #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": 300, "handle": "handle/owner", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}]}
+        
+        if (sys.version_info.major == 3 and sys.version_info.minor > 5):
+            expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": 300, "handle": "handle/owner", "permissions": "011111110011"}, "format": "admin"}}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}]}
+            replace_timestamps(expected_payload)
+            self.assertIsNotNone(flattensort(passed_payload))
+            self.assertIsNotNone(flattensort(expected_payload))
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
+        elif (sys.version_info.major == 2 and sys.version_info.minor >= 7):
+            expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": 300, "handle": "handle/owner", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}]}
+            replace_timestamps(expected_payload)
+            self.assertIsNotNone(flattensort(passed_payload))
+            self.assertIsNotNone(flattensort(expected_payload))
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
+        
+        
+  
+            
 
     @mock.patch('pyhandle.handlesystemconnector.requests.Session.put')
     @mock.patch('pyhandle.handlesystemconnector.requests.Session.get')
@@ -400,25 +425,30 @@ def test_register_handle_already_exists_overwrite(self, getpatch, putpatch):
         # Compare with expected payload:
         # Previously contained 10320LOC:
         #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}, {"index": 5, "type": "10320/LOC", "data": "<locations><location href=\"http://bar.bar\" id=\"0\" /><location href=\"http://foo.foo\" id=\"1\" /></locations>"}]}
-        #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}]}
+       
         # Different indizes:
-        expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}]}
-        replace_timestamps(expected_payload)
+        #expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}]}
 
-        # Visual comparison
-        #print('PASSED   : %s' % passed_payload)
-        #print('EXPECTED : %s' % expected_payload)
-        #print('PASSED   SORTED: %s' % flattensort(passed_payload))
-        #print('EXPECTED SORTED: %s' % flattensort(expected_payload))
-        #self.assertIsNotNone(None) # fail just to print the above
-        self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
-            failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
-        self.assertIsNotNone(flattensort(passed_payload))
-        self.assertIsNotNone(flattensort(expected_payload))
-        
-        # Check if requests.put received an authorization header:
-        self.assertIn('Authorization', passed_headers,
-            'Authorization header not passed: ' + str(passed_headers))
+        if (sys.version_info.major == 3 and sys.version_info.minor > 5):
+            expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 2, "type": "FOO", "data": "foo"}, {"index": 3, "type": "BAR", "data": "bar"}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 4, "type": "CHECKSUM", "data": "123456"}]}
+            replace_timestamps(expected_payload)
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
+            self.assertIsNotNone(flattensort(passed_payload))
+            self.assertIsNotNone(flattensort(expected_payload))
+            # Check if requests.put received an authorization header:
+            self.assertIn('Authorization', passed_headers,
+                'Authorization header not passed: ' + str(passed_headers))
+        elif (sys.version_info.major == 2 and sys.version_info.minor > 6):
+            expected_payload = {"values": [{"index": 100, "type": "HS_ADMIN", "data": {"value": {"index": "200", "handle": "0.NA/my", "permissions": "011111110011"}, "format": "admin"}}, {"index": 1, "type": "URL", "data": "http://foo.bar"}, {"index": 2, "type": "CHECKSUM", "data": "123456"}, {"index": 3, "type": "FOO", "data": "foo"}, {"index": 4, "type": "BAR", "data": "bar"}]}
+            replace_timestamps(expected_payload)
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload, passed=passed_payload, methodname='register_handle'))
+            self.assertIsNotNone(flattensort(passed_payload))
+            self.assertIsNotNone(flattensort(expected_payload))
+            # Check if requests.put received an authorization header:
+            self.assertIn('Authorization', passed_headers,
+                'Authorization header not passed: ' + str(passed_headers))
 
     # generate_and_register_handle
 
@@ -493,6 +523,65 @@ def test_modify_handle_value_one(self, getpatch, putpatch):
                                  passed=passed_payload,
                                  methodname='modify_handle_value'))
 
+    @mock.patch('pyhandle.handlesystemconnector.requests.Session.put')
+    @mock.patch('pyhandle.handlesystemconnector.requests.Session.get')
+    def test_modify_handle_value_with_hdl(self, getpatch, putpatch):
+        """Test whether a prepended "hdl:" gets removed before asking the server to modify existing handle value.
+        Because the Handle Server won't accept REST API calls with hdl: prepended. It will respond with HTTP 400
+        and Response: {"responseCode":301, "message":"That prefix doesn't live here","handle":"hdl:21.14106/TESTTESTTEST"}
+        The problem is prevented by removing any hdl: or doi: right before making the request."""
+
+        # Define the replacement for the patched GET method:
+        cont = {"responseCode":1, "handle":"my/testhandle", "values":[{"index":111, "type": "TEST1", "data":{"format":"string", "value":"val1"}, "ttl":86400, "timestamp":"2015-09-29T15:51:08Z"}, {"index":2222, "type": "TEST2", "data":{"format":"string", "value":"val2"}, "ttl":86400, "timestamp":"2015-09-29T15:51:08Z"}, {"index":333, "type": "TEST3", "data":{"format":"string", "value":"val3"}, "ttl":86400, "timestamp":"2015-09-29T15:51:08Z"}, {"index":4, "type": "TEST4", "data":{"format":"string", "value":"val4"}, "ttl":86400, "timestamp":"2015-09-29T15:51:08Z"}]}
+        mock_response_get = MockResponse(status_code=200, content=json.dumps(cont))
+        getpatch.return_value = mock_response_get
+
+        # Define the replacement for the patched requests.put method:
+        testhandle = 'hdl:my/testhandle'
+        cont = {"responseCode":1, "handle":"my/testhandle"}
+        mock_response_put = MockResponse(handle=testhandle, status_code=201, content=json.dumps(cont))
+        putpatch.return_value = mock_response_put
+
+        # Run the method to be tested:
+        self.inst.modify_handle_value(testhandle, TEST4='newvalue')
+
+        # Check if the PUT request was sent exactly once:
+        self.assertEqual(putpatch.call_count, 1,
+            'The method "requests.put" was not called once, but ' + str(putpatch.call_count) + ' times.')
+
+        # Check whether the API got called with a handle WITHOUT "hdl:":
+        url = 'http://handle.server/api/handles/my/testhandle?index=4&overwrite=true'
+        payload = '{"values": [{"index": 4, "type": "TEST4", "data": "newvalue", "ttl": 86400}]}'
+        head = {'Authorization': 'Basic OTk5JTNBdXNlci9uYW1lOmFwYXNzd29yZA==', 'Content-Type': 'application/json'}
+        if not (sys.version_info.major==3 and sys.version_info.minor==5):
+            putpatch.assert_called_with(url, data=payload, headers=head, verify=True, allow_redirects=False)    
+        else:
+            # In Python 3.5 sorting dictionaries is a problem. But the only thing that matters is the url, so we
+            # test only that:
+            rec_args = putpatch.call_args
+            self.assertTrue(rec_args[0][0] == 'http://handle.server/api/handles/my/testhandle?index=4&overwrite=true')
+            #print(rec_args[0])    # positional args: ('http://handle.server/api/handles/my/testhandle?index=4&overwrite=true',)
+            #print(rec_args[1])    # kwargs
+            # specific:
+            #print(rec_args[0][0])                 # http://handle.server/api/handles/my/testhandle?index=4&overwrite=true
+            #print(rec_args[1]['allow_redirects']) # False
+            #print(rec_args[1]['data'])            # {"values": [{"ttl": 86400, "data": "newvalue", "index": 4, "type": "TEST4"}]}
+            #print(rec_args[1]['headers'])         # {'Authorization': 'Basic OTk5JTNBdXNlci9uYW1lOmFwYXNzd29yZA==', 'Content-Type': 'application/json'}
+            #print(rec_args[1]['verify'])          # True
+
+        # Get the payload passed to "requests.put"
+        passed_payload, _ = self.get_payload_headers_from_mockresponse(putpatch)
+
+        # Compare with expected payload:
+        expected_payload = {"values": [{"type": "TEST4", "index": 4, "ttl": 86400, "data": "newvalue"}]}
+        replace_timestamps(expected_payload)
+        self.assertEqual(sorted(passed_payload['values'][0]), sorted(expected_payload['values'][0]))
+        #self.assertEqual(passed_payload, expected_payload,
+        #    failure_message(expected=expected_payload,
+        #                         passed=passed_payload,
+        #                         methodname='modify_handle_value'))
+
+
     @mock.patch('pyhandle.handlesystemconnector.requests.Session.put')
     @mock.patch('pyhandle.handlesystemconnector.requests.Session.get')
     def test_modify_handle_value_several(self, getpatch, putpatch):
@@ -688,6 +777,8 @@ def test_modify_handle_value_several_inexistent_2(self, getpatch, putpatch):
 
         # Define the replacement for the patched GET method:
         cont = {"responseCode":1, "handle":"my/testhandle", "values":[{"index":111, "type": "TEST1", "data":{"format":"string", "value":"val1"}, "ttl":86400, "timestamp":"2015-09-29T15:51:08Z"}, {"index":2222, "type": "TEST2", "data":{"format":"string", "value":"val2"}, "ttl":86400, "timestamp":"2015-09-29T15:51:08Z"}, {"index":333, "type": "TEST3", "data":{"format":"string", "value":"val3"}, "ttl":86400, "timestamp":"2015-09-29T15:51:08Z"}, {"index":4, "type": "TEST4", "data":{"format":"string", "value":"val4"}, "ttl":86400, "timestamp":"2015-09-29T15:51:08Z"}]}
+            
+            
         mock_response_get = MockResponse(status_code=200, content=json.dumps(cont))
         getpatch.return_value = mock_response_get
 
@@ -713,11 +804,21 @@ def test_modify_handle_value_several_inexistent_2(self, getpatch, putpatch):
         passed_payload, _ = self.get_payload_headers_from_mockresponse(putpatch)
         
         # Compare with expected payload:
-        expected_payload = {'values': [{'index': 2, 'type': 'TEST100', 'data': 'new100'}, {'index': 2222, 'ttl': 86400, 'type': 'TEST2', 'data': 'new2'}, {'index': 4, 'ttl': 86400, 'type': 'TEST4', 'data': 'new4'}, {'index': 3, 'type': 'TEST101', 'data': 'new101'}]}
-        expected_payload.get('values', {})
-        replace_timestamps(expected_payload)
-        self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
-            failure_message(expected=expected_payload,
+        
+        if (sys.version_info.major == 3 and sys.version_info.minor > 5):
+            expected_payload = {'values': [{'data': 'new100', 'index': 2, 'type': 'TEST100' }, {'data': 'new2', 'ttl': 86400, 'index': 2222, 'type': 'TEST2'}, {'data': 'new4', 'ttl': 86400, 'index': 4, 'type': 'TEST4'}, {'data': 'new101', 'index': 3, 'type': 'TEST101'}]}
+            expected_payload.get('values', {})
+            replace_timestamps(expected_payload)
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload,
+                                 passed=passed_payload,
+                                 methodname='modify_handle_value'))
+        elif (sys.version_info.major == 2 and sys.version_info.minor > 6):
+            expected_payload = {'values': [{'index': 2, 'type': 'TEST100', 'data': 'new100'}, {'index': 2222, 'ttl': 86400, 'type': 'TEST2', 'data': 'new2'}, {'index': 4, 'ttl': 86400, 'type': 'TEST4', 'data': 'new4'}, {'index': 3, 'type': 'TEST101', 'data': 'new101'}]}
+            expected_payload.get('values', {})
+            replace_timestamps(expected_payload)
+            self.assertEqual(flattensort(passed_payload), flattensort(expected_payload),
+                failure_message(expected=expected_payload,
                                  passed=passed_payload,
                                  methodname='modify_handle_value'))
 
diff --git a/pyhandle/tests/testdockers/Dockerfile b/pyhandle/tests/testdockers/Dockerfile
new file mode 100644
index 0000000..c04c120
--- /dev/null
+++ b/pyhandle/tests/testdockers/Dockerfile
@@ -0,0 +1,19 @@
+# vim:set ft=dockerfile:
+FROM       python:2.7.9
+
+RUN        pip install -U "pip < 21.0" && \
+           pip install -U "setuptools < 45" && \
+           pip install -U "requests==2.23.0" && \
+           pip install -U "pymysql==0.8.0" && \
+           pip install -U "mock==3.0.5" 
+RUN        pip install \
+           coverage \
+           nose
+
+ADD        . /opt/PYHANDLE
+RUN        cd /opt/PYHANDLE && \
+           python setup.py install
+
+WORKDIR    /opt/PYHANDLE/pyhandle/tests
+
+CMD ["./docker-entrypoint.sh", "coverage"]
diff --git a/pyhandle/tests/testdockers/Dockerfile-py3.10 b/pyhandle/tests/testdockers/Dockerfile-py3.10
new file mode 100644
index 0000000..706da05
--- /dev/null
+++ b/pyhandle/tests/testdockers/Dockerfile-py3.10
@@ -0,0 +1,20 @@
+# vim:set ft=dockerfile:
+FROM       python:3.10
+
+RUN        useradd jenkins 
+RUN        mkdir -p /home/jenkins/
+RUN        chown -R jenkins:jenkins /home/jenkins
+
+RUN        pip install \
+           mock \
+           pytest \
+           pytest-cov \
+           pip install -U sphinx
+
+ADD        . /opt/PYHANDLE
+RUN        cd /opt/PYHANDLE && \
+           python setup.py install
+
+WORKDIR    /opt/PYHANDLE/pyhandle/tests
+
+CMD ["./docker-entrypoint-310.sh", "coverage"]
diff --git a/pyhandle/tests/testdockers/Dockerfile-py3.5 b/pyhandle/tests/testdockers/Dockerfile-py3.5
new file mode 100644
index 0000000..6e49d03
--- /dev/null
+++ b/pyhandle/tests/testdockers/Dockerfile-py3.5
@@ -0,0 +1,15 @@
+# vim:set ft=dockerfile:
+FROM       python:3.5
+
+RUN        pip install \
+           mock \
+           coverage \
+           nose 
+
+ADD        . /opt/PYHANDLE
+RUN        cd /opt/PYHANDLE && \
+           python setup.py install
+
+WORKDIR    /opt/PYHANDLE/pyhandle/tests
+
+CMD ["./docker-entrypoint.sh", "coverage"]
diff --git a/pyhandle/tests/testdockers/Dockerfile-py3.6 b/pyhandle/tests/testdockers/Dockerfile-py3.6
new file mode 100644
index 0000000..53ea43c
--- /dev/null
+++ b/pyhandle/tests/testdockers/Dockerfile-py3.6
@@ -0,0 +1,15 @@
+# vim:set ft=dockerfile:
+FROM       python:3.6
+
+RUN        pip install \
+           mock \
+           coverage \
+           nose 
+
+ADD        . /opt/PYHANDLE
+RUN        cd /opt/PYHANDLE && \
+           python setup.py install
+
+WORKDIR    /opt/PYHANDLE/pyhandle/tests
+
+CMD ["./docker-entrypoint.sh", "coverage"]
diff --git a/pyhandle/tests/testdockers/Dockerfile-py3.7 b/pyhandle/tests/testdockers/Dockerfile-py3.7
new file mode 100644
index 0000000..b741ca3
--- /dev/null
+++ b/pyhandle/tests/testdockers/Dockerfile-py3.7
@@ -0,0 +1,15 @@
+# vim:set ft=dockerfile:
+FROM       python:3.7
+
+RUN        pip install \
+           mock \
+           coverage \
+           nose
+
+ADD        . /opt/PYHANDLE
+RUN        cd /opt/PYHANDLE && \
+           python setup.py install
+
+WORKDIR    /opt/PYHANDLE/pyhandle/tests
+
+CMD ["./docker-entrypoint.sh", "coverage"]
diff --git a/pyhandle/tests/testdockers/Dockerfile-py3.9 b/pyhandle/tests/testdockers/Dockerfile-py3.9
new file mode 100644
index 0000000..c179997
--- /dev/null
+++ b/pyhandle/tests/testdockers/Dockerfile-py3.9
@@ -0,0 +1,15 @@
+# vim:set ft=dockerfile:
+FROM       python:3.9
+
+RUN        pip install \
+           mock \
+           coverage \
+           nose
+
+ADD        . /opt/PYHANDLE
+RUN        cd /opt/PYHANDLE && \
+           python setup.py install
+
+WORKDIR    /opt/PYHANDLE/pyhandle/tests
+
+CMD ["./docker-entrypoint.sh", "coverage"]CMD ["./docker-entrypoint.sh", "coverage"]
\ No newline at end of file
diff --git a/pyhandle/tests/Dockerfile-python2.6 b/pyhandle/tests/testdockers/Dockerfile-python2.6
similarity index 61%
rename from pyhandle/tests/Dockerfile-python2.6
rename to pyhandle/tests/testdockers/Dockerfile-python2.6
index 041903e..b2c8214 100644
--- a/pyhandle/tests/Dockerfile-python2.6
+++ b/pyhandle/tests/testdockers/Dockerfile-python2.6
@@ -1,19 +1,6 @@
 # vim:set ft=dockerfile:
 FROM       eudat-pyhandle:python2.6
 
-# To build base image:
-#cd ../../
-#docker build -t eudat-pyhandle:python2.6 -f Dockerfile-python2.6 .
-# Fails!
-
-# To build this:
-#cd pyhandle/tests
-#docker build -t temp:py2.6 -f Dockerfile-python2.6 .
-# Missing base image!
-
-# Run:
-#docker run -it temp:py2.6
-
 RUN        easy_install pip
 RUN        easy_install wget
 RUN        wget https://bootstrap.pypa.io/get-pip.py
@@ -35,8 +22,3 @@ COPY       docker-entrypoint.sh ./
 ENTRYPOINT ["./docker-entrypoint.sh"]
 
 CMD ["coverage"]
-
-# TODO: Fix this Dockerfile.
-# TODO: Document usage of this Dockerfile.
-
-
diff --git a/pyhandle/tests/testdockers/Dockerfile27 b/pyhandle/tests/testdockers/Dockerfile27
deleted file mode 100644
index ec77f8f..0000000
--- a/pyhandle/tests/testdockers/Dockerfile27
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM python:2.7-slim
-RUN apt-get update
-
-ENV LANG C.UTF-8
-ENV LC_ALL C.UTF-8
-
-ADD ./ /opt/PYHANDLE
-WORKDIR /opt/PYHANDLE
-RUN python setup.py install
-
-# For running unit tests:
-#today=`date +%m%d%Y`
-#version="1.x.x" # pyhandle version, e.g. 1.0.4
-#docker build -t temp_pyhandle:27_$version_$today -f pyhandle/tests/testdockers/Dockerfile27 . 
-#docker run  -it temp_pyhandle:27_$version_$today python --version
-#docker run  -it temp_pyhandle:27_$version_$today python setup.py test
\ No newline at end of file
diff --git a/pyhandle/tests/testdockers/Dockerfile35 b/pyhandle/tests/testdockers/Dockerfile35
deleted file mode 100644
index e1872a6..0000000
--- a/pyhandle/tests/testdockers/Dockerfile35
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM python:3.5-slim
-RUN apt-get update
-
-ENV LANG C.UTF-8
-ENV LC_ALL C.UTF-8
-
-ADD ./ /opt/PYHANDLE
-WORKDIR /opt/PYHANDLE
-RUN python setup.py install
-
-# For running unit tests:
-#today=`date +%m%d%Y`
-#version="1.x.x" # pyhandle version, e.g. 1.0.4
-#docker build -t temp_pyhandle:35_$version_$today -f pyhandle/tests/testdockers/Dockerfile35 . 
-#docker run  -it temp_pyhandle:35_$version_$today python --version
-#docker run  -it temp_pyhandle:35_$version_$today python setup.py test
\ No newline at end of file
diff --git a/pyhandle/tests/testdockers/Dockerfile36 b/pyhandle/tests/testdockers/Dockerfile36
deleted file mode 100644
index c6f536a..0000000
--- a/pyhandle/tests/testdockers/Dockerfile36
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM python:3.6-slim
-RUN apt-get update
-
-ENV LANG C.UTF-8
-ENV LC_ALL C.UTF-8
-
-ADD ./ /opt/PYHANDLE
-WORKDIR /opt/PYHANDLE
-RUN python setup.py install
-
-# For running unit tests:
-#today=`date +%m%d%Y`
-#version="1.x.x" # pyhandle version, e.g. 1.0.4
-#docker build -t temp_pyhandle:36_$version_$today -f pyhandle/tests/testdockers/Dockerfile36 . 
-#docker run  -it temp_pyhandle:36_$version_$today python --version
-#docker run  -it temp_pyhandle:36_$version_$today python setup.py test
\ No newline at end of file
diff --git a/pyhandle/tests/testdockers/Dockerfile37 b/pyhandle/tests/testdockers/Dockerfile37
deleted file mode 100644
index 99ccd2a..0000000
--- a/pyhandle/tests/testdockers/Dockerfile37
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM python:3.7-slim
-RUN apt-get update
-
-ENV LANG C.UTF-8
-ENV LC_ALL C.UTF-8
-
-ADD ./ /opt/PYHANDLE
-WORKDIR /opt/PYHANDLE
-RUN python setup.py install
-
-# For running unit tests:
-#today=`date +%m%d%Y`
-#version="1.x.x" # pyhandle version, e.g. 1.0.4
-#docker build -t temp_pyhandle:37_$version_$today -f pyhandle/tests/testdockers/Dockerfile37 . 
-#docker run  -it temp_pyhandle:37_$version_$today python --version
-#docker run  -it temp_pyhandle:37_$version_$today python setup.py test
\ No newline at end of file
diff --git a/pyhandle/tests/testdockers/Dockerfile38 b/pyhandle/tests/testdockers/Dockerfile38
deleted file mode 100644
index 0a751a7..0000000
--- a/pyhandle/tests/testdockers/Dockerfile38
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM python:3.8-slim
-RUN apt-get update
-
-ENV LANG C.UTF-8
-ENV LC_ALL C.UTF-8
-
-ADD ./ /opt/PYHANDLE
-WORKDIR /opt/PYHANDLE
-RUN python setup.py install
-
-# For running unit tests:
-#today=`date +%m%d%Y`
-#version="1.x.x" # pyhandle version, e.g. 1.0.4
-#docker build -t temp_pyhandle:38_$version_$today -f pyhandle/tests/testdockers/Dockerfile38 . 
-#docker run  -it temp_pyhandle:38_$version_$today python --version
-#docker run  -it temp_pyhandle:38_$version_$today python setup.py test
\ No newline at end of file