<reponame>statiksof/ECAS-B2SHARE
""" Helper class to interact with B2SHARE REST API to create draft records
from ECAS user workspace.
Please refer to https://eudat.eu/services/userdoc/b2share-usage
 for more information.

.. Note:: this module is not considered as B2SHARE client and it is specific
 to ECAS use case.

Author: <NAME> (DKRZ), 2019

"""

import requests
import json
import os
import logging

from requests import Request, Session
from . import exceptions


from urllib.parse import urljoin
from requests.exceptions import HTTPError

logging.basicConfig(level=logging.INFO)


class EcasShare (object):

    """ ECAS B2SHARE main class """

    # Initialize

    def __init__(self, url=None, token_file=None):
        """
        Initialize the client.

        :param url: URL of the B2SHARE instance.  One of the following
        hostnames can be used to identify the B2SHARE instance:

                     * b2share.eudat.eu - the hostname of the production site.
                     * trng-b2share.eudat.eu - the base url of the training
                     site. Use this URL for testing.

        :param token_file: B2SHARE API ACCESS token
        """

        # Default path in container
        CONTAINER_PATH = '/home/jovyan/work/conf'

        # TODO: set up b2share url (prod or test), token and payload?
        if url is None:
            self.B2SHARE_URL = 'https://eudat-b2share-test.csc.fi'
        else:
            self.B2SHARE_URL = url

        if token_file is None:
            self.token_path = os.path.join(CONTAINER_PATH, 'token.txt')
        else:
            self.token_path = token_file

    # Token

    def retrieve_access_token(self):
        """ Read the token from a given file named 'token' """

        token_file = open(self.token_path, 'r')
        return token_file.read().strip()

    # communities

    def list_communities(self, token=None):
        """
        List all the communities, without any filtering.

        :param token: Optional: B2SHARE API ACCESS token
        :return: list of communities in json
        """

        url = urljoin(self.B2SHARE_URL, 'api/communities')

        if token is None:
            try:
                req = self.__send_get_request(url)
                req.raise_for_status()
            except requests.exceptions.HTTPError as err:
                print(err)

        else:
            payload = {'access_token': token}
            try:
                req = self.__send_get_request(url, params=payload)
                req.raise_for_status()
            except requests.exceptions.HTTPError as err:
                print(err)

        if req.status_code == 200:
            return req.json()

    def retrieve_community_specific_records(self, community_id):
        """
        List all records of a specific community.

        :param community_id: community id. Can be retrieved from the list of
        communities
        :exc:`~ecasb2share.ecasb2shareclient.EcasShare.list_communities`
        :return:  the list of records (in JSON format) or an exception message
        """

        payload = {'q': 'community:' + community_id}
        url = urljoin(self.B2SHARE_URL, '/api/records/')

        try:
            req = self.__send_get_request(url,
                                          params=payload)
            req.raise_for_status()
            number_of_records = req.json()['hits']['total']
            print("Total number of records in this community: {}".format(number_of_records))
            if number_of_records > 0:
                return req.json()
            else:
                print("No records in this community")
        except requests.exceptions.HTTPError as err:
            print(err)

    def get_community_schema(self, community_id):
        """
        Retrieves the JSON schema of records approved by a specific community.

        :param community_id: community id. Can be found from the list of
        communities
                            :exc:`~ecasb2share.ecasb2shareclient.EcasShare.list_communities`
        :return: community schema in json format.
        """

        base = self.B2SHARE_URL
        url = '/api/communities/' + community_id + '/schemas/last'

        try:
            req = self.__send_get_request(urljoin(base, url))
            req.raise_for_status()
        except requests.exceptions.HTTPError as err:
            print(err)

        if req.status_code == 200:
            return req.json()

    # records

    def list_all_records(self, size=None):
        """
        List all the records, without any filtering
        TODO add pagination
        :param size: Optional
        :return: list of records in json format.
        """

        url = urljoin(self.B2SHARE_URL, 'api/records')
        if size:
            payload = {'size': size, 'page': 1}
        else:
            payload = {'size': 10, 'page': 1}

        try:
            req = self.__send_get_request(url, params=payload)
            req.raise_for_status()
            return req.json()
        except requests.exceptions.HTTPError as err:
            print(err)

    def get_specific_record(self, record_id, draft=True):
        """ List the metadata of the record specified by RECORD_ID.

        :param record_id: record id.
        :param draft: True/False to specify which type of record to search for.
               Default: True.
        :return: list of records.

         """

        header = {'Content-Type': 'application-json'}
        token = self.retrieve_access_token().rstrip()
        payload = {'access_token': token}

        if draft:
            url = urljoin(self.B2SHARE_URL, 'api/records/' + record_id +
                          '/draft')
        else:
            url = urljoin(self.B2SHARE_URL, 'api/records/' + record_id)

        try:
            req = self.__send_get_request(url, params=payload, headers=header)
            if req is not None:
                req.raise_for_status()
                return json.loads(req.text)
        except requests.exceptions.HTTPError as err:
            print(err)

    def get_record_pid(self, record_id):
        """
        Get the pid from the record metadata (published).

        :param record_id: record id
        :return: epicPID, prefix/suffix
        """

        record = self.get_specific_record(record_id)

        try:
            "{ePIC_PID}".format(**record['metadata'])
            return record['metadata']['ePIC_PID']
        except KeyError as e:
            msg = " missing {} key".format(e)
            raise exceptions.MetadataKeyMissingException(msg=msg)

    def create_draft_record(self, community_id, title):
        """
        Create a new record with minimal metadata, in the draft state.

        :param community_id:
        :param title: title for the record
        :return: record_id, filebucket_id
        """

        token = self.retrieve_access_token().rstrip()
        header = {"Content-Type": "application/json"}
        payload = {'access_token': token}
        metadata = {"titles": [{"title": title}],
                    "community": community_id,
                    "open_access": True}
        url = urljoin(self.B2SHARE_URL, '/api/records/')
        try:
            req = self.__send_post_request(url,
                                           data=json.dumps(metadata),
                                           params=payload,
                                           headers=header)

            req.raise_for_status()
            record_id = req.json()
            filebucket_id = self.get_filebucketid_from_record(record_id['id'])
            print("Draft record created:\n" + record_id['id'])
            print('filebucketid:\n' + filebucket_id)

        except requests.exceptions.HTTPError as err:
            print(err)

        if req.status_code == 201:
            logging.info("Draft record successfully created!")
            return record_id['id'], filebucket_id

    def create_draft_record_with_pid(self, title=None, original_pid=None,
                                     metadata_json=None):
        """

        Create a draft record and specifying the original pid.
        Adapted from DataCite schema:
           * relatedIdentifierType: Handle
           * relationType: isDerivedFrom

        :param title: title for the record.
        :param original_pid: PID (prefix/suffix) of the input Dataset.
        :return: record_id and filebucket_id
        """

        ECAS_COMMUNITY_ID = 'd2c6e694-0c0a-4884-ad15-ddf498008320'
        token = self.retrieve_access_token().rstrip()
        header = {"Content-Type": "application/json"}
        payload = {"access_token": token}
        url = urljoin(self.B2SHARE_URL, '/api/records/')

        if metadata_json:
            metadata = self.load_metadata_from_json(metadata_json)
            self.validate_metadata(metadata)

            related_identifiers = metadata['related_identifiers']

            for pid in range(len(related_identifiers)):
                self.check_pid_syntax(
                    related_identifiers[pid]['related_identifier'])

            try:
                req = self.__send_post_request(url,
                                               data=json.dumps(metadata),
                                               params=payload,
                                               headers=header)

                req.raise_for_status()
            except requests.exceptions.HTTPError as err:
                print(err)

        else:
            metadata = {"titles": [{"title": title}],
                        "community": ECAS_COMMUNITY_ID,
                        "related_identifiers": [
                            {
                                "related_identifier": original_pid,
                                "related_identifier_type": "Handle",
                                "relation_type": "IsDerivedFrom"
                            }
            ],
                "open_access": True
            }
            try:
                req = self.__send_post_request(url,
                                               data=json.dumps(metadata),
                                               params=payload,
                                               headers=header)

                req.raise_for_status()
            except requests.exceptions.HTTPError as err:
                print(err)

        record_id = req.json()
        filebucket_id = self.get_filebucketid_from_record(record_id['id'])
        print("Draft record created:\n" + record_id['id'])
        print('filebucketid:\n' + filebucket_id)

        if req.status_code == 201:
            logging.info("Draft record successfully created!")
            return record_id['id'], filebucket_id

    def submit_draft_for_publication(self, record_id):
        """

        :param record_id: record id
        :return: request status (HTTP response)
        """

        header = {'Content-Type': 'application/json-patch+json'}
        commit = '[{"op": "add", "path": "/publication_state", "value": "submitted"}]'
        token = self.retrieve_access_token().rstrip()
        url = self.B2SHARE_URL + record_id + "/draft"
        payload = {"access_token": token}

        try:
            req = requests.patch(
                url, data=commit, params=payload, headers=header)
            req.raise_for_status()
        except requests.exceptions.HTTPError as err:
            print(err)

        return req.status_code

    def delete_draft_record(self, record_id):
        """

        :param record_id: record id
        :return: request status
        """

        url = urljoin(self.B2SHARE_URL, '/api/records/' + record_id + '/draft')
        token = self.retrieve_access_token().rstrip()
        payload = {'access_token': token}
        header = {"Content-Type": "application/json"}

        req = requests.delete(url, params=payload, headers=header)
        logging.info(req.status_code)
        return req.status_code

    def delete_published_record(self, record_id):
        """
        Notes: only a site administrator can delete a published record.

        :param record_id: the id of the draft record to delete
        :return: request status
        """

        url = urljoin(self.B2SHARE_URL, '/api/records/' + record_id)
        token = self.retrieve_access_token().rstrip()
        payload = {'access_token': token}
        header = {"Content-Type": "application/json"}

        req = requests.delete(url, params=payload, headers=header)

        return req.status_code

    def search_records(self):
        """
        List all the records, without any filtering

        """

        token = self.retrieve_access_token().rstrip()
        header = {"Content-Type": "application/json"}
        payload = {'access_token': token}

        url = urljoin(self.B2SHARE_URL, '/api/records')
        req = self.__send_get_request(url,
                                      params=payload, headers=header)
        return req.json()

    def get_filebucketid_from_record(self, record_id):
        """
        TODO add exception when record not found

        :param record_id:  identifier for a specific record, which
         can be in draft or published state
        :return: filebucket id.
        """

        record = self.get_specific_record(record_id)

        if record is not None:
            filebucket = record["links"]["files"].split('/')[-1]
            return filebucket

    def search_drafts(self):
        """
        Search for all drafts (unpublished records) that are accessible
        by the requestor.
        Usually this means own records only.

        :return: the list of matching drafts (in JSON format).
        """

        token = self.retrieve_access_token().rstrip()
        header = {"Content-Type": "application/json"}
        payload = {'draft': 1, 'access_token': token}
        url = urljoin(self.B2SHARE_URL, '/api/records/?drafts')

        req = self.__send_get_request(url,
                                      params=payload,
                                      headers=header)

        result = json.loads(req.text)
        print(result["hits"]["total"])
        return result

    def search_specific_record(self, search_value):

        payload = {'q': search_value}
        url = urljoin(self.B2SHARE_URL, '/api/records')

        req = self.__send_get_request(url,
                                      params=payload)
        return req.json()

    # files

    def add_file_to_draft_record(self, file_path, filebucket_id):
        """

        :param file_path: path to the file to be uploaded.
        :param filebucket_id: identifier for a set of files.
               Each record has its own file set, usually found
               in the links -> files section


        :return: request status
        """

        header = {'Accept': 'application/json', 'Content-Type': 'octet-stream'}
        token = self.retrieve_access_token().rstrip()
        payload = {'access_token': token}
        upload_file = {"file": open(file_path, 'rb')}
        file_name = os.path.basename(file_path)
        url = urljoin(self.B2SHARE_URL, '/api/files/' + filebucket_id)

        req = self.__send_put_request(url + '/' + file_name,
                                      files=upload_file,
                                      params=payload,
                                      headers=header)

        return req.json()

    def list_files_in_bucket(self, filebucket_id):
        """
        List the files uploaded into a record object.

        :param filebucket_id: identifier for a set of files.
               Each record has its own file set, usually found in
               the links -> files section

        :return: information about all the files in the record object
        """

        token = self.retrieve_access_token()
        payload = {'access_token': token}

        if filebucket_id:
            url = urljoin(self.B2SHARE_URL, '/api/files/' + filebucket_id)
            try:
                req = self.__send_get_request(url, params=payload)
                return req.json()
            except HTTPError as err:
                msg = '{empty request}'.format(err)
                raise exceptions.MetadataKeyMissingException(msg=msg)
        else:
            print("Filebucket ID is None!")
    # requests

    @staticmethod
    def __send_get_request(url, params=None, headers=None):

        s = Session()
        REQUEST_METHOD = 'GET'

        # Build the request
        _request = Request(REQUEST_METHOD, url, params=params, headers=headers)
        prepared_request = _request.prepare()

        try:
            response = s.send(prepared_request)

            # If the response was successful, no Exception will be raised
            response.raise_for_status()
        except HTTPError as http_err:
            print('HTTP error occurred:' + str(http_err))
        except Exception as err:
            print('Other error occurred: ' + err)
        else:
            return response

    @staticmethod
    def __send_put_request(url, files, params, headers):

        s = Session()

        REQUEST_METHOD = 'PUT'

        # Build the request
        _request = Request(REQUEST_METHOD, url, files=files,
                           params=params, headers=headers)
        prepared_request = _request.prepare()

        try:
            response = s.send(prepared_request)

        # If the response was successful, no Exception will be raised
            response.raise_for_status()
        except HTTPError as http_err:
            print('HTTP error occurred:' + http_err)
        except Exception as err:
            print('Other error occurred: ' + err)
        else:
            logging.info('Success!')
            return response

    @staticmethod
    def __send_post_request(url, data, params, headers):

        s = Session()

        REQUEST_MEHOD = 'POST'

        # Build he request
        _request = Request(REQUEST_MEHOD, url, data=data,
                           params=params, headers=headers)
        prepared_request = _request.prepare()

        try:
            response = s.send(prepared_request)
        # If the response was successful, no Exception will be raised
            response.raise_for_status()
        except HTTPError as http_err:
            print('HTTP error occurred:' + http_err)
        except Exception as err:
            print('Other error occurred: ' + err)
        else:
            logging.info('Record created!')
            return response

    @staticmethod
    def __response_status(response):

        if response:
            print("Success!")
        else:
            print('An error has occured.')

    # metadata

    @staticmethod
    def load_metadata_from_json(metadata_json=None):
        if metadata_json:
            with open(metadata_json, 'r') as metadata_json:
                return json.load(metadata_json)

    def validate_metadata(self, metadata=None, metadata_file=None):
        """
        Check if mandatory metadata are passed.

        :param metadata: Optional: metadata as dict (key, value)
        :param metadata_file: Optional. metadata in a json file.
        :return: metadata in json format.
        """

        if metadata_file:
            metadata = self.load_metadata_from_json(metadata_json=metadata_file)

        try:
            "{titles} {related_identifiers} {community} {open_access}".format(**metadata)
        except KeyError as e:
            msg = "missing {} key".format(e)
            raise exceptions.MetadataException(msg=msg)

        return metadata

    @staticmethod
    def check_pid_syntax(pid):
        """
        Checks if the syntax of the Handle is correct.
        Handles should be in this form:
             prefix/suffix

        :param pid: Value of the handle to be checked
        :raise: :exc:`~ecasb2share.exceptions.PIDSyntaxException`
        :return: True, otherwise, exception raised.
        """

        correct_syntax = 'prefix/suffix'

        try:
            arr = pid.split('/')
        except AttributeError:
            raise exceptions.PidSyntaxException(
                msg='The provided handle is None',
                correct_syntax=correct_syntax)

        if len(arr) < 2:
            msg = 'No slash'
            raise exceptions.PidSyntaxException(
                msg=msg, pid=pid, correct_syntax=correct_syntax)

        if len(arr[0]) == 0:
            msg = 'Empty prefix'
            raise exceptions.PidSyntaxException(
                msg=msg, pid=pid, correct_syntax=correct_syntax)

        if len(arr[1]) == 0:
            msg = 'Empty suffix'
            raise exceptions.PidSyntaxException(
                msg=msg, pid=pid, correct_syntax=correct_syntax)

        return True

""" Helper class to interact with B2SHARE REST API to create draft records
from ECAS user workspace.
Please refer to https://eudat.eu/services/userdoc/b2share-usage
 for more information.

.. Note:: this module is not considered as B2SHARE client and it is specific
 to ECAS use case.

Author: <NAME> (DKRZ), 2019

"""

import requests
import json
import os
import logging

from requests import Request, Session
from . import exceptions


from urllib.parse import urljoin
from requests.exceptions import HTTPError

logging.basicConfig(level=logging.INFO)


class EcasShare (object):

    """ ECAS B2SHARE main class """

    # Initialize

    def __init__(self, url=None, token_file=None):
        """
        Initialize the client.

        :param url: URL of the B2SHARE instance.  One of the following
        hostnames can be used to identify the B2SHARE instance:

                     * b2share.eudat.eu - the hostname of the production site.
                     * trng-b2share.eudat.eu - the base url of the training
                     site. Use this URL for testing.

        :param token_file: B2SHARE API ACCESS token
        """

        # Default path in container
        CONTAINER_PATH = '/home/jovyan/work/conf'

        # TODO: set up b2share url (prod or test), token and payload?
        if url is None:
            self.B2SHARE_URL = 'https://eudat-b2share-test.csc.fi'
        else:
            self.B2SHARE_URL = url

        if token_file is None:
            self.token_path = os.path.join(CONTAINER_PATH, 'token.txt')
        else:
            self.token_path = token_file

    # Token

    def retrieve_access_token(self):
        """ Read the token from a given file named 'token' """

        token_file = open(self.token_path, 'r')
        return token_file.read().strip()

    # communities

    def list_communities(self, token=None):
        """
        List all the communities, without any filtering.

        :param token: Optional: B2SHARE API ACCESS token
        :return: list of communities in json
        """

        url = urljoin(self.B2SHARE_URL, 'api/communities')

        if token is None:
            try:
                req = self.__send_get_request(url)
                req.raise_for_status()
            except requests.exceptions.HTTPError as err:
                print(err)

        else:
            payload = {'access_token': token}
            try:
                req = self.__send_get_request(url, params=payload)
                req.raise_for_status()
            except requests.exceptions.HTTPError as err:
                print(err)

        if req.status_code == 200:
            return req.json()

    def retrieve_community_specific_records(self, community_id):
        """
        List all records of a specific community.

        :param community_id: community id. Can be retrieved from the list of
        communities
        :exc:`~ecasb2share.ecasb2shareclient.EcasShare.list_communities`
        :return:  the list of records (in JSON format) or an exception message
        """

        payload = {'q': 'community:' + community_id}
        url = urljoin(self.B2SHARE_URL, '/api/records/')

        try:
            req = self.__send_get_request(url,
                                          params=payload)
            req.raise_for_status()
            number_of_records = req.json()['hits']['total']
            print("Total number of records in this community: {}".format(number_of_records))
            if number_of_records > 0:
                return req.json()
            else:
                print("No records in this community")
        except requests.exceptions.HTTPError as err:
            print(err)

    def get_community_schema(self, community_id):
        """
        Retrieves the JSON schema of records approved by a specific community.

        :param community_id: community id. Can be found from the list of
        communities
                            :exc:`~ecasb2share.ecasb2shareclient.EcasShare.list_communities`
        :return: community schema in json format.
        """

        base = self.B2SHARE_URL
        url = '/api/communities/' + community_id + '/schemas/last'

        try:
            req = self.__send_get_request(urljoin(base, url))
            req.raise_for_status()
        except requests.exceptions.HTTPError as err:
            print(err)

        if req.status_code == 200:
            return req.json()

    # records

    def list_all_records(self, size=None):
        """
        List all the records, without any filtering
        TODO add pagination
        :param size: Optional
        :return: list of records in json format.
        """

        url = urljoin(self.B2SHARE_URL, 'api/records')
        if size:
            payload = {'size': size, 'page': 1}
        else:
            payload = {'size': 10, 'page': 1}

        try:
            req = self.__send_get_request(url, params=payload)
            req.raise_for_status()
            return req.json()
        except requests.exceptions.HTTPError as err:
            print(err)

    def get_specific_record(self, record_id, draft=True):
        """ List the metadata of the record specified by RECORD_ID.

        :param record_id: record id.
        :param draft: True/False to specify which type of record to search for.
               Default: True.
        :return: list of records.

         """

        header = {'Content-Type': 'application-json'}
        token = self.retrieve_access_token().rstrip()
        payload = {'access_token': token}

        if draft:
            url = urljoin(self.B2SHARE_URL, 'api/records/' + record_id +
                          '/draft')
        else:
            url = urljoin(self.B2SHARE_URL, 'api/records/' + record_id)

        try:
            req = self.__send_get_request(url, params=payload, headers=header)
            if req is not None:
                req.raise_for_status()
                return json.loads(req.text)
        except requests.exceptions.HTTPError as err:
            print(err)

    def get_record_pid(self, record_id):
        """
        Get the pid from the record metadata (published).

        :param record_id: record id
        :return: epicPID, prefix/suffix
        """

        record = self.get_specific_record(record_id)

        try:
            "{ePIC_PID}".format(**record['metadata'])
            return record['metadata']['ePIC_PID']
        except KeyError as e:
            msg = " missing {} key".format(e)
            raise exceptions.MetadataKeyMissingException(msg=msg)

    def create_draft_record(self, community_id, title):
        """
        Create a new record with minimal metadata, in the draft state.

        :param community_id:
        :param title: title for the record
        :return: record_id, filebucket_id
        """

        token = self.retrieve_access_token().rstrip()
        header = {"Content-Type": "application/json"}
        payload = {'access_token': token}
        metadata = {"titles": [{"title": title}],
                    "community": community_id,
                    "open_access": True}
        url = urljoin(self.B2SHARE_URL, '/api/records/')
        try:
            req = self.__send_post_request(url,
                                           data=json.dumps(metadata),
                                           params=payload,
                                           headers=header)

            req.raise_for_status()
            record_id = req.json()
            filebucket_id = self.get_filebucketid_from_record(record_id['id'])
            print("Draft record created:\n" + record_id['id'])
            print('filebucketid:\n' + filebucket_id)

        except requests.exceptions.HTTPError as err:
            print(err)

        if req.status_code == 201:
            logging.info("Draft record successfully created!")
            return record_id['id'], filebucket_id

    def create_draft_record_with_pid(self, title=None, original_pid=None,
                                     metadata_json=None):
        """

        Create a draft record and specifying the original pid.
        Adapted from DataCite schema:
           * relatedIdentifierType: Handle
           * relationType: isDerivedFrom

        :param title: title for the record.
        :param original_pid: PID (prefix/suffix) of the input Dataset.
        :return: record_id and filebucket_id
        """

        ECAS_COMMUNITY_ID = 'd2c6e694-0c0a-4884-ad15-ddf498008320'
        token = self.retrieve_access_token().rstrip()
        header = {"Content-Type": "application/json"}
        payload = {"access_token": token}
        url = urljoin(self.B2SHARE_URL, '/api/records/')

        if metadata_json:
            metadata = self.load_metadata_from_json(metadata_json)
            self.validate_metadata(metadata)

            related_identifiers = metadata['related_identifiers']

            for pid in range(len(related_identifiers)):
                self.check_pid_syntax(
                    related_identifiers[pid]['related_identifier'])

            try:
                req = self.__send_post_request(url,
                                               data=json.dumps(metadata),
                                               params=payload,
                                               headers=header)

                req.raise_for_status()
            except requests.exceptions.HTTPError as err:
                print(err)

        else:
            metadata = {"titles": [{"title": title}],
                        "community": ECAS_COMMUNITY_ID,
                        "related_identifiers": [
                            {
                                "related_identifier": original_pid,
                                "related_identifier_type": "Handle",
                                "relation_type": "IsDerivedFrom"
                            }
            ],
                "open_access": True
            }
            try:
                req = self.__send_post_request(url,
                                               data=json.dumps(metadata),
                                               params=payload,
                                               headers=header)

                req.raise_for_status()
            except requests.exceptions.HTTPError as err:
                print(err)

        record_id = req.json()
        filebucket_id = self.get_filebucketid_from_record(record_id['id'])
        print("Draft record created:\n" + record_id['id'])
        print('filebucketid:\n' + filebucket_id)

        if req.status_code == 201:
            logging.info("Draft record successfully created!")
            return record_id['id'], filebucket_id

    def submit_draft_for_publication(self, record_id):
        """

        :param record_id: record id
        :return: request status (HTTP response)
        """

        header = {'Content-Type': 'application/json-patch+json'}
        commit = '[{"op": "add", "path": "/publication_state", "value": "submitted"}]'
        token = self.retrieve_access_token().rstrip()
        url = self.B2SHARE_URL + record_id + "/draft"
        payload = {"access_token": token}

        try:
            req = requests.patch(
                url, data=commit, params=payload, headers=header)
            req.raise_for_status()
        except requests.exceptions.HTTPError as err:
            print(err)

        return req.status_code

    def delete_draft_record(self, record_id):
        """

        :param record_id: record id
        :return: request status
        """

        url = urljoin(self.B2SHARE_URL, '/api/records/' + record_id + '/draft')
        token = self.retrieve_access_token().rstrip()
        payload = {'access_token': token}
        header = {"Content-Type": "application/json"}

        req = requests.delete(url, params=payload, headers=header)
        logging.info(req.status_code)
        return req.status_code

    def delete_published_record(self, record_id):
        """
        Notes: only a site administrator can delete a published record.

        :param record_id: the id of the draft record to delete
        :return: request status
        """

        url = urljoin(self.B2SHARE_URL, '/api/records/' + record_id)
        token = self.retrieve_access_token().rstrip()
        payload = {'access_token': token}
        header = {"Content-Type": "application/json"}

        req = requests.delete(url, params=payload, headers=header)

        return req.status_code

    def search_records(self):
        """
        List all the records, without any filtering

        """

        token = self.retrieve_access_token().rstrip()
        header = {"Content-Type": "application/json"}
        payload = {'access_token': token}

        url = urljoin(self.B2SHARE_URL, '/api/records')
        req = self.__send_get_request(url,
                                      params=payload, headers=header)
        return req.json()

    def get_filebucketid_from_record(self, record_id):
        """
        TODO add exception when record not found

        :param record_id:  identifier for a specific record, which
         can be in draft or published state
        :return: filebucket id.
        """

        record = self.get_specific_record(record_id)

        if record is not None:
            filebucket = record["links"]["files"].split('/')[-1]
            return filebucket

    def search_drafts(self):
        """
        Search for all drafts (unpublished records) that are accessible
        by the requestor.
        Usually this means own records only.

        :return: the list of matching drafts (in JSON format).
        """

        token = self.retrieve_access_token().rstrip()
        header = {"Content-Type": "application/json"}
        payload = {'draft': 1, 'access_token': token}
        url = urljoin(self.B2SHARE_URL, '/api/records/?drafts')

        req = self.__send_get_request(url,
                                      params=payload,
                                      headers=header)

        result = json.loads(req.text)
        print(result["hits"]["total"])
        return result

    def search_specific_record(self, search_value):

        payload = {'q': search_value}
        url = urljoin(self.B2SHARE_URL, '/api/records')

        req = self.__send_get_request(url,
                                      params=payload)
        return req.json()

    # files

    def add_file_to_draft_record(self, file_path, filebucket_id):
        """

        :param file_path: path to the file to be uploaded.
        :param filebucket_id: identifier for a set of files.
               Each record has its own file set, usually found
               in the links -> files section


        :return: request status
        """

        header = {'Accept': 'application/json', 'Content-Type': 'octet-stream'}
        token = self.retrieve_access_token().rstrip()
        payload = {'access_token': token}
        upload_file = {"file": open(file_path, 'rb')}
        file_name = os.path.basename(file_path)
        url = urljoin(self.B2SHARE_URL, '/api/files/' + filebucket_id)

        req = self.__send_put_request(url + '/' + file_name,
                                      files=upload_file,
                                      params=payload,
                                      headers=header)

        return req.json()

    def list_files_in_bucket(self, filebucket_id):
        """
        List the files uploaded into a record object.

        :param filebucket_id: identifier for a set of files.
               Each record has its own file set, usually found in
               the links -> files section

        :return: information about all the files in the record object
        """

        token = self.retrieve_access_token()
        payload = {'access_token': token}

        if filebucket_id:
            url = urljoin(self.B2SHARE_URL, '/api/files/' + filebucket_id)
            try:
                req = self.__send_get_request(url, params=payload)
                return req.json()
            except HTTPError as err:
                msg = '{empty request}'.format(err)
                raise exceptions.MetadataKeyMissingException(msg=msg)
        else:
            print("Filebucket ID is None!")
    # requests

    @staticmethod
    def __send_get_request(url, params=None, headers=None):

        s = Session()
        REQUEST_METHOD = 'GET'

        # Build the request
        _request = Request(REQUEST_METHOD, url, params=params, headers=headers)
        prepared_request = _request.prepare()

        try:
            response = s.send(prepared_request)

            # If the response was successful, no Exception will be raised
            response.raise_for_status()
        except HTTPError as http_err:
            print('HTTP error occurred:' + str(http_err))
        except Exception as err:
            print('Other error occurred: ' + err)
        else:
            return response

    @staticmethod
    def __send_put_request(url, files, params, headers):

        s = Session()

        REQUEST_METHOD = 'PUT'

        # Build the request
        _request = Request(REQUEST_METHOD, url, files=files,
                           params=params, headers=headers)
        prepared_request = _request.prepare()

        try:
            response = s.send(prepared_request)

        # If the response was successful, no Exception will be raised
            response.raise_for_status()
        except HTTPError as http_err:
            print('HTTP error occurred:' + http_err)
        except Exception as err:
            print('Other error occurred: ' + err)
        else:
            logging.info('Success!')
            return response

    @staticmethod
    def __send_post_request(url, data, params, headers):

        s = Session()

        REQUEST_MEHOD = 'POST'

        # Build he request
        _request = Request(REQUEST_MEHOD, url, data=data,
                           params=params, headers=headers)
        prepared_request = _request.prepare()

        try:
            response = s.send(prepared_request)
        # If the response was successful, no Exception will be raised
            response.raise_for_status()
        except HTTPError as http_err:
            print('HTTP error occurred:' + http_err)
        except Exception as err:
            print('Other error occurred: ' + err)
        else:
            logging.info('Record created!')
            return response

    @staticmethod
    def __response_status(response):

        if response:
            print("Success!")
        else:
            print('An error has occured.')

    # metadata

    @staticmethod
    def load_metadata_from_json(metadata_json=None):
        if metadata_json:
            with open(metadata_json, 'r') as metadata_json:
                return json.load(metadata_json)

    def validate_metadata(self, metadata=None, metadata_file=None):
        """
        Check if mandatory metadata are passed.

        :param metadata: Optional: metadata as dict (key, value)
        :param metadata_file: Optional. metadata in a json file.
        :return: metadata in json format.
        """

        if metadata_file:
            metadata = self.load_metadata_from_json(metadata_json=metadata_file)

        try:
            "{titles} {related_identifiers} {community} {open_access}".format(**metadata)
        except KeyError as e:
            msg = "missing {} key".format(e)
            raise exceptions.MetadataException(msg=msg)

        return metadata

    @staticmethod
    def check_pid_syntax(pid):
        """
        Checks if the syntax of the Handle is correct.
        Handles should be in this form:
             prefix/suffix

        :param pid: Value of the handle to be checked
        :raise: :exc:`~ecasb2share.exceptions.PIDSyntaxException`
        :return: True, otherwise, exception raised.
        """

        correct_syntax = 'prefix/suffix'

        try:
            arr = pid.split('/')
        except AttributeError:
            raise exceptions.PidSyntaxException(
                msg='The provided handle is None',
                correct_syntax=correct_syntax)

        if len(arr) < 2:
            msg = 'No slash'
            raise exceptions.PidSyntaxException(
                msg=msg, pid=pid, correct_syntax=correct_syntax)

        if len(arr[0]) == 0:
            msg = 'Empty prefix'
            raise exceptions.PidSyntaxException(
                msg=msg, pid=pid, correct_syntax=correct_syntax)

        if len(arr[1]) == 0:
            msg = 'Empty suffix'
            raise exceptions.PidSyntaxException(
                msg=msg, pid=pid, correct_syntax=correct_syntax)

        return True
Helper class to interact with B2SHARE REST API to create draft records from ECAS user workspace. Please refer to https://eudat.eu/services/userdoc/b2share-usage  for more information.  .. Note:: this module is not considered as B2SHARE client and it is specific  to ECAS use case.  Author: <NAME> (DKRZ), 2019 ECAS B2SHARE main class # Initialize Initialize the client.          :param url: URL of the B2SHARE instance.  One of the following         hostnames can be used to identify the B2SHARE instance:                       * b2share.eudat.eu - the hostname of the production site.                      * trng-b2share.eudat.eu - the base url of the training                      site. Use this URL for testing.          :param token_file: B2SHARE API ACCESS token # Default path in container # TODO: set up b2share url (prod or test), token and payload? # Token Read the token from a given file named 'token' # communities List all the communities, without any filtering.          :param token: Optional: B2SHARE API ACCESS token         :return: list of communities in json List all records of a specific community.          :param community_id: community id. Can be retrieved from the list of         communities         :exc:`~ecasb2share.ecasb2shareclient.EcasShare.list_communities`         :return:  the list of records (in JSON format) or an exception message Retrieves the JSON schema of records approved by a specific community.          :param community_id: community id. Can be found from the list of         communities                             :exc:`~ecasb2share.ecasb2shareclient.EcasShare.list_communities`         :return: community schema in json format. # records List all the records, without any filtering         TODO add pagination         :param size: Optional         :return: list of records in json format. List the metadata of the record specified by RECORD_ID.          :param record_id: record id.         :param draft: True/False to specify which type of record to search for.                Default: True.         :return: list of records. Get the pid from the record metadata (published).          :param record_id: record id         :return: epicPID, prefix/suffix Create a new record with minimal metadata, in the draft state.          :param community_id:         :param title: title for the record         :return: record_id, filebucket_id Create a draft record and specifying the original pid.         Adapted from DataCite schema:            * relatedIdentifierType: Handle            * relationType: isDerivedFrom          :param title: title for the record.         :param original_pid: PID (prefix/suffix) of the input Dataset.         :return: record_id and filebucket_id :param record_id: record id         :return: request status (HTTP response) :param record_id: record id         :return: request status Notes: only a site administrator can delete a published record.          :param record_id: the id of the draft record to delete         :return: request status List all the records, without any filtering TODO add exception when record not found          :param record_id:  identifier for a specific record, which          can be in draft or published state         :return: filebucket id. Search for all drafts (unpublished records) that are accessible         by the requestor.         Usually this means own records only.          :return: the list of matching drafts (in JSON format). # files :param file_path: path to the file to be uploaded.         :param filebucket_id: identifier for a set of files.                Each record has its own file set, usually found                in the links -> files section           :return: request status List the files uploaded into a record object.          :param filebucket_id: identifier for a set of files.                Each record has its own file set, usually found in                the links -> files section          :return: information about all the files in the record object # requests # Build the request # If the response was successful, no Exception will be raised # Build the request # If the response was successful, no Exception will be raised # Build he request # If the response was successful, no Exception will be raised # metadata Check if mandatory metadata are passed.          :param metadata: Optional: metadata as dict (key, value)         :param metadata_file: Optional. metadata in a json file.         :return: metadata in json format. Checks if the syntax of the Handle is correct.         Handles should be in this form:              prefix/suffix          :param pid: Value of the handle to be checked         :raise: :exc:`~ecasb2share.exceptions.PIDSyntaxException`         :return: True, otherwise, exception raised.