github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/sawtooth-core-master/cli/sawtooth_cli/rest_client.py (about)

     1  # Copyright 2016 Intel Corporation
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  # ------------------------------------------------------------------------------
    15  
    16  import json
    17  from base64 import b64encode
    18  from http.client import RemoteDisconnected
    19  import requests
    20  # pylint: disable=no-name-in-module,import-error
    21  # needed for the google.protobuf imports to pass pylint
    22  from google.protobuf.message import Message as BaseMessage
    23  
    24  from sawtooth_cli.exceptions import CliException
    25  
    26  
    27  class RestClient(object):
    28      def __init__(self, base_url=None, user=None):
    29          self._base_url = base_url or 'http://localhost:8008'
    30  
    31          if user:
    32              b64_string = b64encode(user.encode()).decode()
    33              self._auth_header = 'Basic {}'.format(b64_string)
    34          else:
    35              self._auth_header = None
    36  
    37      def list_blocks(self, limit=None):
    38          """Return a block generator.
    39  
    40          Args:
    41              limit (int): The page size of requests
    42          """
    43          return self._get_data('/blocks', limit=limit)
    44  
    45      def get_block(self, block_id):
    46          return self._get('/blocks/' + block_id)['data']
    47  
    48      def list_batches(self):
    49          return self._get_data('/batches')
    50  
    51      def get_batch(self, batch_id):
    52          return self._get('/batches/' + batch_id)['data']
    53  
    54      def list_peers(self):
    55          return self._get('/peers')['data']
    56  
    57      def get_status(self):
    58          return self._get('/status')['data']
    59  
    60      def list_transactions(self):
    61          return self._get_data('/transactions')
    62  
    63      def get_transaction(self, transaction_id):
    64          return self._get('/transactions/' + transaction_id)['data']
    65  
    66      def list_state(self, subtree=None, head=None):
    67          return self._get('/state', address=subtree, head=head)
    68  
    69      def get_leaf(self, address, head=None):
    70          return self._get('/state/' + address, head=head)
    71  
    72      def get_statuses(self, batch_ids, wait=None):
    73          """Fetches the committed status for a list of batch ids.
    74  
    75          Args:
    76              batch_ids (list of str): The ids to get the status of.
    77              wait (optional, int): Indicates that the api should wait to
    78                  respond until the batches are committed or the specified
    79                  time in seconds has elapsed.
    80  
    81          Returns:
    82              list of dict: Dicts with 'id' and 'status' properties
    83          """
    84          return self._post('/batch_statuses', batch_ids, wait=wait)['data']
    85  
    86      def send_batches(self, batch_list):
    87          """Sends a list of batches to the validator.
    88  
    89          Args:
    90              batch_list (:obj:`BatchList`): the list of batches
    91  
    92          Returns:
    93              dict: the json result data, as a dict
    94          """
    95          if isinstance(batch_list, BaseMessage):
    96              batch_list = batch_list.SerializeToString()
    97  
    98          return self._post('/batches', batch_list)
    99  
   100      def _get(self, path, **queries):
   101          code, json_result = self._submit_request(
   102              self._base_url + path,
   103              params=self._format_queries(queries),
   104          )
   105  
   106          # concat any additional pages of data
   107          while code == 200 and 'next' in json_result.get('paging', {}):
   108              previous_data = json_result.get('data', [])
   109              code, json_result = self._submit_request(
   110                  json_result['paging']['next'])
   111              json_result['data'] = previous_data + json_result.get('data', [])
   112  
   113          if code == 200:
   114              return json_result
   115          elif code == 404:
   116              raise CliException(
   117                  '{}: There is no resource with the identifier "{}"'.format(
   118                      self._base_url, path.split('/')[-1]))
   119          else:
   120              raise CliException(
   121                  "{}: {} {}".format(self._base_url, code, json_result))
   122  
   123      def _get_data(self, path, **queries):
   124          url = self._base_url + path
   125          params = self._format_queries(queries)
   126  
   127          while url:
   128              code, json_result = self._submit_request(
   129                  url,
   130                  params=params,
   131              )
   132  
   133              if code == 404:
   134                  raise CliException(
   135                      '{}: There is no resource with the identifier "{}"'.format(
   136                          self._base_url, path.split('/')[-1]))
   137              elif code != 200:
   138                  raise CliException(
   139                      "{}: {} {}".format(self._base_url, code, json_result))
   140  
   141              for item in json_result.get('data', []):
   142                  yield item
   143  
   144              url = json_result['paging'].get('next', None)
   145  
   146      def _post(self, path, data, **queries):
   147          if isinstance(data, bytes):
   148              headers = {'Content-Type': 'application/octet-stream'}
   149          else:
   150              data = json.dumps(data).encode()
   151              headers = {'Content-Type': 'application/json'}
   152          headers['Content-Length'] = '%d' % len(data)
   153  
   154          code, json_result = self._submit_request(
   155              self._base_url + path,
   156              params=self._format_queries(queries),
   157              data=data,
   158              headers=headers,
   159              method='POST')
   160  
   161          if code == 200 or code == 201 or code == 202:
   162              return json_result
   163          else:
   164              raise CliException("({}): {}".format(code, json_result))
   165  
   166      def _submit_request(self, url, params=None, data=None, headers=None,
   167                          method="GET"):
   168          """Submits the given request, and handles the errors appropriately.
   169  
   170          Args:
   171              url (str): the request to send.
   172              params (dict): params to be passed along to get/post
   173              data (bytes): the data to include in the request.
   174              headers (dict): the headers to include in the request.
   175              method (str): the method to use for the request, "POST" or "GET".
   176  
   177          Returns:
   178              tuple of (int, str): The response status code and the json parsed
   179                  body, or the error message.
   180  
   181          Raises:
   182              `CliException`: If any issues occur with the URL.
   183          """
   184          if headers is None:
   185              headers = {}
   186  
   187          if self._auth_header is not None:
   188              headers['Authorization'] = self._auth_header
   189  
   190          try:
   191              if method == 'POST':
   192                  result = requests.post(
   193                      url, params=params, data=data, headers=headers)
   194              elif method == 'GET':
   195                  result = requests.get(
   196                      url, params=params, data=data, headers=headers)
   197              result.raise_for_status()
   198              return (result.status_code, result.json())
   199          except requests.exceptions.HTTPError as e:
   200              return (e.response.status_code, e.response.reason)
   201          except RemoteDisconnected as e:
   202              raise CliException(e)
   203          except (requests.exceptions.MissingSchema,
   204                  requests.exceptions.InvalidURL) as e:
   205              raise CliException(e)
   206          except requests.exceptions.ConnectionError as e:
   207              raise CliException(
   208                  ('Unable to connect to "{}": '
   209                   'make sure URL is correct').format(self._base_url))
   210  
   211      @staticmethod
   212      def _format_queries(queries):
   213          queries = {k: v for k, v in queries.items() if v is not None}
   214          return queries if queries else ''