text: # Copyright 2013-2018 CERN for the benefit of the ATLAS collaboration.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #    http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  #
  # Authors:
  # - Vincent Garonne <vgaronne@gmail.com>, 2013-2018
  # - Ralph Vigne <ralph.vigne@cern.ch>, 2013-2015
  # - Mario Lassnig <mario.lassnig@cern.ch>, 2013-2018
  # - Martin Barisits <martin.barisits@cern.ch>, 2013-2018
  # - Yun-Pin Sun <winter0128@gmail.com>, 2013
  # - Thomas Beermann <thomas.beermann@cern.ch>, 2013
  # - Cedric Serfon <cedric.serfon@cern.ch>, 2014-2015
  # - Joaquin Bogado <jbogado@linti.unlp.edu.ar>, 2014-2018
  # - Brian Bockelman <bbockelm@cse.unl.edu>, 2018
  # - Eric Vaandering <ericvaandering@gmail.com>, 2018
  # - Asket Agarwal <asket.agarwal96@gmail.com>, 2018
  # - Hannes Hansen <hannes.jakob.hansen@cern.ch>, 2018
  #
  # PY3K COMPATIBLE
  
  from __future__ import print_function
  
  try:
      from urllib import quote_plus
  except ImportError:
      from urllib.parse import quote_plus
  
  from json import dumps, loads
  from requests.status_codes import codes
  
  from rucio.client.baseclient import BaseClient
  from rucio.client.baseclient import choice
  from rucio.common.utils import build_url, render_json, render_json_list, date_to_str
  
  
  class DIDClient(BaseClient):
  
      """DataIdentifier client class for working with data identifiers"""
  
      DIDS_BASEURL = 'dids'
      ARCHIVES_BASEURL = 'archives'
  
      def __init__(self, rucio_host=None, auth_host=None, account=None, ca_cert=None,
                   auth_type=None, creds=None, timeout=600, user_agent='rucio-clients'):
          super(DIDClient, self).__init__(rucio_host, auth_host, account, ca_cert,
                                          auth_type, creds, timeout, user_agent)
  
      def list_dids(self, scope, filters, type='collection', long=False, recursive=False):
          """
          List all data identifiers in a scope which match a given pattern.
  
          :param scope: The scope name.
          :param filters: A dictionary of key/value pairs like {'name': 'file_name','rse-expression': 'tier0'}.
          :param type: The type of the did: 'all'(container, dataset or file)|'collection'(dataset or container)|'dataset'|'container'|'file'
          :param long: Long format option to display more information for each DID.
          :param recursive: Recursively list DIDs content.
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), 'dids', 'search'])
          payload = {}
          if long:
              payload['long'] = 1
  
          for k, v in list(filters.items()):
              if k in ('created_before', 'created_after'):
                  payload[k] = date_to_str(v)
              else:
                  payload[k] = v
          payload['type'] = type
          payload['recursive'] = recursive
  
          url = build_url(choice(self.list_hosts), path=path, params=payload)
  
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              dids = self._load_json_data(r)
              return dids
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def add_did(self, scope, name, type, statuses=None, meta=None, rules=None, lifetime=None, dids=None, rse=None):
          """
          Add data identifier for a dataset or container.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :paran type: The data identifier type (file|dataset|container).
          :param statuses: Dictionary with statuses, e.g.g {'monotonic':True}.
          :meta: Meta-data associated with the data identifier is represented using key/value pairs in a dictionary.
          :rules: Replication rules associated with the data identifier. A list of dictionaries, e.g., [{'copies': 2, 'rse_expression': 'TIERS1'}, ].
          :param lifetime: DID's lifetime (in seconds).
          :param dids: The content.
          :param rse: The RSE name when registering replicas.
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name)])
          url = build_url(choice(self.list_hosts), path=path)
          # Build json
          data = {'type': type}
          if statuses:
              data['statuses'] = statuses
          if meta:
              data['meta'] = meta
          if rules:
              data['rules'] = rules
          if lifetime:
              data['lifetime'] = lifetime
          if dids:
              data['dids'] = dids
          if rse:
              data['rse'] = rse
          r = self._send_request(url, type='POST', data=render_json(**data))
          if r.status_code == codes.created:
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def add_dids(self, dids):
          """
          Bulk add datasets/containers.
          """
          path = '/'.join([self.DIDS_BASEURL])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='POST', data=render_json_list(dids))
          if r.status_code == codes.created:
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def add_dataset(self, scope, name, statuses=None, meta=None, rules=None, lifetime=None, files=None, rse=None):
          """
          Add data identifier for a dataset.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :param statuses: Dictionary with statuses, e.g.g {'monotonic':True}.
          :meta: Meta-data associated with the data identifier is represented using key/value pairs in a dictionary.
          :rules: Replication rules associated with the data identifier. A list of dictionaries, e.g., [{'copies': 2, 'rse_expression': 'TIERS1'}, ].
          :param lifetime: DID's lifetime (in seconds).
          :param files: The content.
          :param rse: The RSE name when registering replicas.
          """
          return self.add_did(scope=scope, name=name, type='DATASET',
                              statuses=statuses, meta=meta, rules=rules,
                              lifetime=lifetime, dids=files, rse=rse)
  
      def add_datasets(self, dsns):
          """
          Bulk add datasets.
  
          :param dsns: A list of datasets.
          """
          return self.add_dids(dids=[dict(list(dsn.items()) + [('type', 'DATASET')]) for dsn in dsns])
  
      def add_container(self, scope, name, statuses=None, meta=None, rules=None, lifetime=None):
          """
          Add data identifier for a container.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :param statuses: Dictionary with statuses, e.g.g {'monotonic':True}.
          :param meta: Meta-data associated with the data identifier is represented using key/value pairs in a dictionary.
          :param rules: Replication rules associated with the data identifier. A list of dictionaries, e.g., [{'copies': 2, 'rse_expression': 'TIERS1'}, ].
          :param lifetime: DID's lifetime (in seconds).
          """
          return self.add_did(scope=scope, name=name, type='CONTAINER', statuses=statuses, meta=meta, rules=rules, lifetime=lifetime)
  
      def add_containers(self, cnts):
          """
          Bulk add containers.
  
          :param cnts: A list of containers.
          """
          return self.add_dids(dids=[dict(list(cnts.items()) + [('type', 'CONTAINER')]) for cnt in cnts])
  
      def attach_dids(self, scope, name, dids, rse=None):
          """
          Attach data identifier.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :param dids: The content.
          :param rse: The RSE name when registering replicas.
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'dids'])
          url = build_url(choice(self.list_hosts), path=path)
          data = {'dids': dids}
          if rse:
              data['rse'] = rse
          r = self._send_request(url, type='POST', data=render_json(**data))
          if r.status_code == codes.created:
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def detach_dids(self, scope, name, dids):
          """
          Detach data identifier
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :param dids: The content.
          """
  
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'dids'])
          url = build_url(choice(self.list_hosts), path=path)
          data = {'dids': dids}
          r = self._send_request(url, type='DEL', data=render_json(**data))
          if r.status_code == codes.ok:
              return True
          exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
          raise exc_cls(exc_msg)
  
      def attach_dids_to_dids(self, attachments, ignore_duplicate=False):
          """
          Add dids to dids.
  
          :param attachments: The attachments.
              attachments is: [attachment, attachment, ...]
              attachment is: {'scope': scope, 'name': name, 'dids': dids}
              dids is: [{'scope': scope, 'name': name}, ...]
              :param ignore_duplicate: If True, ignore duplicate entries.
          """
          path = '/'.join([self.DIDS_BASEURL, 'attachments'])
          url = build_url(choice(self.list_hosts), path=path)
          data = {'ignore_duplicate': ignore_duplicate, 'attachments': attachments}
          r = self._send_request(url, type='POST', data=dumps(data))
          if r.status_code in (codes.ok, codes.no_content, codes.created):
              return True
  
          exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
          raise exc_cls(exc_msg)
  
      def add_files_to_datasets(self, attachments, ignore_duplicate=False):
          """
          Add files to datasets.
  
          :param attachments: The attachments.
              attachments is: [attachment, attachment, ...]
              attachment is: {'scope': scope, 'name': name, 'dids': dids}
              dids is: [{'scope': scope, 'name': name}, ...]
          :param ignore_duplicate: If True, ignore duplicate entries.
          """
          return self.attach_dids_to_dids(attachments=attachments,
                                          ignore_duplicate=ignore_duplicate)
  
      def add_datasets_to_containers(self, attachments):
          """
          Add datasets_to_containers.
  
          :param attachments: The attachments.
              attachments is: [attachment, attachment, ...]
              attachment is: {'scope': scope, 'name': name, 'dids': dids}
              dids is: [{'scope': scope, 'name': name}, ...]
          """
          return self.attach_dids_to_dids(attachments=attachments)
  
      def add_containers_to_containers(self, attachments):
          """
          Add containers_to_containers.
  
          :param attachments: The attachments.
              attachments is: [attachment, attachment, ...]
              attachment is: {'scope': scope, 'name': name, 'dids': dids}
              dids is: [{'scope': scope, 'name': name}, ...]
          """
          return self.attach_dids_to_dids(attachments=attachments)
  
      def add_files_to_dataset(self, scope, name, files, rse=None):
          """
          Add files to datasets.
  
          :param scope: The scope name.
          :param name: The dataset name.
          :param files: The content.
          :param rse: The RSE name when registering replicas.
          """
          return self.attach_dids(scope=scope, name=name, dids=files, rse=rse)
  
      def add_files_to_archive(self, scope, name, files):
          """
          Add files to archive.
  
          :param scope: The scope name.
          :param name: The dataset name.
          :param files: The content.
          """
          return self.attach_dids(scope=scope, name=name, dids=files)
  
      def add_datasets_to_container(self, scope, name, dsns):
          """
          Add datasets to container.
  
          :param scope: The scope name.
          :param name: The dataset name.
          :param dsns: The content.
          """
          return self.attach_dids(scope=scope, name=name, dids=dsns)
  
      def add_containers_to_container(self, scope, name, cnts):
          """
          Add containers to container.
  
          :param scope: The scope name.
          :param name: The dataset name.
          :param dsns: The content.
          """
          return self.attach_dids(scope=scope, name=name, dids=cnts)
  
      def list_content(self, scope, name):
          """
          List data identifier contents.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          """
  
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'dids'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
          raise exc_cls(exc_msg)
  
      def list_content_history(self, scope, name):
          """
          List data identifier contents history.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          """
  
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'dids', 'history'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
          raise exc_cls(exc_msg)
  
      def list_files(self, scope, name, long=None):
          """
          List data identifier file contents.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :param long: A boolean to choose if GUID is returned or not.
          """
  
          payload = {}
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'files'])
          if long:
              payload['long'] = True
          url = build_url(choice(self.list_hosts), path=path, params=payload)
  
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def get_did(self, scope, name):
          """
          Retrieve a single data identifier.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          """
  
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name)])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return next(self._load_json_data(r))
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def get_metadata(self, scope, name):
          """
          Get data identifier metadata
  
          :param scope: The scope name.
          :param name: The data identifier name.
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'meta'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              meta = self._load_json_data(r)
              return next(meta)
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def set_metadata(self, scope, name, key, value, recursive=False):
          """
          Set data identifier metadata
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :param key: the key.
          :param value: the value.
          :param recursive: Option to propagate the metadata change to content.
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'meta', key])
          url = build_url(choice(self.list_hosts), path=path)
          data = dumps({'value': value, 'recursive': recursive})
          r = self._send_request(url, type='POST', data=data)
          if r.status_code == codes.created:
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def set_status(self, scope, name, **kwargs):
          """
          Set data identifier status
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :param kwargs:  Keyword arguments of the form status_name=value.
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'status'])
          url = build_url(choice(self.list_hosts), path=path)
          data = dumps(kwargs)
          r = self._send_request(url, type='PUT', data=data)
          if r.status_code in (codes.ok, codes.no_content, codes.created):
              return True
  
          exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
          raise exc_cls(exc_msg)
  
      def close(self, scope, name):
          """
          close dataset/container
  
          :param scope: The scope name.
          :param name: The dataset/container name.
          """
          return self.set_status(scope=scope, name=name, open=False)
  
      def delete_metadata(self, scope, name, key):
          """
          Delete data identifier metadata
  
          :param scope: The scope name.
          :param name: The data identifier.
          :param key: the key.
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'meta', key])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='DEL')
  
          if r.status_code == codes.ok:
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def list_did_rules(self, scope, name):
          """
          List the associated rules of a data identifier.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          """
  
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'rules'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def list_associated_rules_for_file(self, scope, name):
          """
          List the associated rules a file is affected from..
  
          :param scope: The scope name.
          :param name:  The file name.
          """
  
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'associated_rules'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def get_dataset_by_guid(self, guid):
          """
          Get the parent datasets for a given GUID.
          :param guid: The GUID.
  
          :returns: A did
          """
  
          path = '/'.join([self.DIDS_BASEURL, guid, 'guid'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def scope_list(self, scope, name=None, recursive=False):
          """
          List data identifiers in a scope.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          :param recursive: boolean, True or False.
          """
  
          payload = {}
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), ''])
          if name:
              payload['name'] = name
          if recursive:
              payload['recursive'] = True
          url = build_url(choice(self.list_hosts), path=path, params=payload)
  
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def list_parent_dids(self, scope, name):
          """
          List parent dataset/containers of a did.
  
          :param scope: The scope.
          :param name:  The name.
          """
  
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'parents'])
          url = build_url(choice(self.list_hosts), path=path)
  
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def create_did_sample(self, input_scope, input_name, output_scope, output_name, nbfiles):
          """
          Create a sample from an input collection.
  
          :param input_scope: The scope of the input DID.
          :param input_name: The name of the input DID.
          :param output_scope: The scope of the output dataset.
          :param output_name: The name of the output dataset.
          :param account: The account.
          :param nbfiles: The number of files to register in the output dataset.
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(input_scope), quote_plus(input_name), quote_plus(output_scope), quote_plus(output_name), str(nbfiles), 'sample'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='POST', data=dumps({}))
          if r.status_code == codes.created:
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def resurrect(self, dids):
          """
          Resurrect a list of dids.
  
          :param dids:  A list of dids [{'scope': scope, 'name': name}, ...]
          """
          path = '/'.join([self.DIDS_BASEURL, 'resurrect'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='POST', data=dumps(dids))
          if r.status_code == codes.created:
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def add_temporary_dids(self, dids):
          """
          Bulk add temporary data identifiers.
  
          :param dids: A list of dids.
          """
          url = build_url(choice(self.list_hosts), path='tmp_dids')
          r = self._send_request(url, type='POST', data=dumps(dids))
          if r.status_code == codes.created:
              return True
          exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
          raise exc_cls(exc_msg)
  
      def list_archive_content(self, scope, name):
          """
          List archive contents.
  
          :param scope: The scope name.
          :param name: The data identifier name.
          """
          path = '/'.join([self.ARCHIVES_BASEURL, quote_plus(scope), quote_plus(name), 'files'])
          url = build_url(choice(self.list_hosts), path=path)
  
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return self._load_json_data(r)
          exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
          raise exc_cls(exc_msg)
  
      def add_did_meta(self, scope, name, meta):
          """
          Insert metadata to the json column of a did, updates key if already present
  
          :param scope: the scope of did
          :param name: the name of the did
          :param meta: the metadata to be inserted or updated(in json format)
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'did_meta'])
          url = build_url(choice(self.list_hosts), path=path)
          data = dumps(meta)
          r = self._send_request(url, type='POST', data=data)
          if r.status_code == codes.created:
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def delete_did_meta(self, scope, name, key):
          """
          Delete a key from the metadata column
  
          :param scope: the scope of did
          :param name: the name of the did
          :param key: the key to be deleted
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'did_meta'])
          url = build_url(choice(self.list_hosts), path=path, params={'key': key})
          r = self._send_request(url, type='DEL')
  
          if r.status_code == codes.ok:
              print(r.text)
              return True
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def get_did_meta(self, scope, name):
          """
          Get all metadata for a given did
          :param scope: the scope of did
          :param name: the name of the did
          """
          path = '/'.join([self.DIDS_BASEURL, quote_plus(scope), quote_plus(name), 'did_meta'])
          url = build_url(choice(self.list_hosts), path=path)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              meta = self._load_json_data(r)
              return next(meta)
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  
      def list_dids_by_meta(self, scope=None, select={}):
          """
          Gets all dids matching the values of the provided metadata keys
          :param scope: the scope of the search
          :param select: the key value pairs to search with(query in json format)
          """
          path = '/'.join([self.DIDS_BASEURL, 'list_dids_by_meta'])
          payload = {}
          if scope is not None:
              payload['scope'] = scope
          payload['select'] = dumps(select)
          url = build_url(choice(self.list_hosts), path=path, params=payload)
          r = self._send_request(url, type='GET')
          if r.status_code == codes.ok:
              return loads(next(self._load_json_data(r)))
          else:
              exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
              raise exc_cls(exc_msg)
  