text: """
  .. module:: sfdc
     :synopsis: A Salesforce Rest API module
  
  .. moduleauthor:: Aaron Caffrey <acaffrey@salesforce.com>, Jose Garcia Ponce <jgarciaponce@salesforce.com>,
      Colin Cheevers <ccheevers@salesforce.com>, Tania Prince <tania.prince@salesforce.com>
  
  """
  from __future__ import absolute_import
  
  from . import chatter
  from . import commons
  from . import jobs
  from . import wave
  
  import json
  import logging
  import re
  import requests
  
  try:
      from urllib.parse import urlencode
  except ImportError:
      from urllib import urlencode
  
  DEFAULT_API_VERSION = commons.DEFAULT_API_VERSION
  SOBJ_SERVICE = '/services/data/v%s/sobjects%s'
  REVOKE_SERVICE = '/services/oauth2/revoke'
  VERSIONS_SERVICE = '/services/data/'
  QUERY_SERVICE = '/services/data/v%s/query/?%s'
  SEARCH_SERVICE = '/services/data/v%s/search/?%s'
  TOOLING_ANONYMOUS = '/services/data/v%s/tooling/executeAnonymous/?%s'
  APPROVAL_SERVICE = '/services/data/v%s/process/approvals/'
  
  INSERT_BINARY_BODY_TEMPLATE = """--boundary_string
  Content-Disposition: form-data; name="entity_%s";
  Content-Type: application/json
  
  %s
  
  --boundary_string
  Content-Type: %s
  Content-Disposition: form-data; name="%s"; filename="%s"
  
  %s
  
  --boundary_string--"""
  
  
  class ApprovalProcess(commons.BaseRequest):
      """ Returns a list of all approval processes. Can also be used to submit a particular record # noqa
      if that entity supports an approval process and one has already been defined. It also supports # noqa
      specifying a collection of different Process Approvals requests to have them all executed in bulk.
  
          .. versionadded:: 1.0.0
      """
  
      def __init__(self, session_id, instance_url, **kwargs):
          super(
              ApprovalProcess,
              self).__init__(
              session_id,
              instance_url,
              **kwargs)
  
          self.service = APPROVAL_SERVICE % self.api_version
  
          if self.request_body is None:
              self.http_method = 'GET'
          elif self.request_body is not None:
              self.http_method = 'POST'
  
  
  class Client(object):
      """ The client class from which all API calls to a Salesforce organisation are made.
  
          .. versionadded:: 1.0.0
      """
      def __init__(self, *args, **kwargs):
          """ Constructor.
  
              :Parameters:
                  - `*username` (`string`) - Salesforce username.
                  - `*password` (`string`) - Salesforce password.
                  - `*login_url` (`string`) - Salesforce login URL.
                  - `*client_id` (`string`) - Salesforce client ID.
                  - `*client_secret` (`string`) - Salesforce client secret.
                  - `\**kwargs` - kwargs (see below)
  
              :Keyword Arguments:
                  * *protocol* (`string`) --
                      Protocol (future use)
                  * *proxies* (`dict`) --
                      A dict containing proxies to be used by `requests` module. Ex:
                          `{"https": "example.org:443"}`
                      Default: `None`
                  * *version* (`string`) --
                     SFDC API version to use e.g. '39.0'
          """
  
          self.username = args[0]
          self.password = args[1]
          self.client_id = args[2]
          self.client_secret = args[3]
          self.protocol = kwargs.get('protocol')
          self.proxies = kwargs.get('proxies')
          self.instance_url = None
          self.logger = logging.getLogger('sfdc_py')
          self.logger.setLevel(logging.FATAL)
          self.logger.addHandler(logging.StreamHandler())
          self.client_api_version = None
          self.client_kwargs = kwargs
          self.session_id = None
          self.chatter = chatter.Chatter(self)
          self.jobs = jobs.Jobs(self)
          self.wave = wave.Wave(self)
  
      def set_instance_url(self, url):
          """ Strips the protocol from `url` and assigns the value to `self.instance_url`
  
            :param url: Instance URL used to make requests (eg. `'https://eu11.salesforce.com'`)
            :type url: string
          """
  
          host_only_regex = re.compile('(?:https://)(.*)(?:/*)')
          match = re.match(host_only_regex, url)
          instance_url = match.group(1)
          self.instance_url = instance_url
  
      @commons.kwarg_adder
      def login(self, **kwargs):
          """ Performs a login request.
  
            :param: **kwargs: kwargs
            :type: **kwargs: dict
            :return: Login response
            :rtype: (dict, Login)
          """
  
          login_response = Login(
              self.username,
              self.password,
              self.client_id,
              self.client_secret,
              **kwargs
          )
          req = login_response.request()
  
          if req is not None:
              self.session_id = login_response.get_session_id()
              self.set_instance_url(req.get('instance_url', str()))
              self.set_api_version()
  
          return req, login_response
  
      @commons.kwarg_adder
      def set_api_version(self, **kwargs):
          """
          Sets the api version to be used by the client. If not provided, it will get the latest version
          available
  
          :return: set version kwarg on client if not defined
          """
          # If 'version' was already in the client kwargs, then 'commons.kwarg_adder' decorator will take care of
          # passing it around between functions. Therefore, an else statement is not needed here.
          if 'version' not in self.client_kwargs:
              service = 'https://' + self.instance_url + VERSIONS_SERVICE
              headers = {'Content-Type': 'application/json'}
              r = requests.get(service, headers=headers, proxies=self.proxies)
              if r.status_code == 200:
                  versions = []
                  for i in r.json():
                      versions.append(i['version'])
                  self.client_kwargs.update({'version': max(versions)})
              else:
                  # return a known recent api version
                  self.client_kwargs.update({'version': DEFAULT_API_VERSION})
  
      @commons.kwarg_adder
      def logout(self, **kwargs):
          """ Performs a logout request.
  
            :param: **kwargs: kwargs
            :type: **kwargs: dict
            :return: Logout response
            :rtype: (dict, Logout)
          """
  
          logout_response = Logout(self.session_id, self.instance_url, **kwargs)
          req = logout_response.request()
          return req, logout_response
  
      @commons.kwarg_adder
      def query(self, qs, **kwargs):
          """ Performs a query request.
  
            :param: qs: Query string. eg `'SELECT Id FROM Account LIMIT 10'`
            :type: qs: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
            :return: Query response
            :rtype: (dict, Query)
          """
  
          q = Query(self.session_id, self.instance_url, qs, **kwargs)
          req = q.request()
          return req, q
  
      @commons.kwarg_adder
      def query_more(self, qs, **kwargs):
          """ Performs a query more request.
  
            :param: qs: Query string. eg `'SELECT Id FROM Lead'`
            :type: qs: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
            :return: QueryMore response
            :rtype: ([dict], QueryMore)
          """
  
          qm = QueryMore(self.session_id, self.instance_url, qs, **kwargs)
          req = qm.request()
          return req, qm
  
      @commons.kwarg_adder
      def sobjects(self, **kwargs):
          """ Prepares an SObject controller with which make various API requests.
  
            :param: **kwargs: kwargs
            :type: **kwargs: dict
            :return: SObjects response
            :rtype: SObjectController
          """
  
          _id = kwargs['id'] if 'id' in kwargs else None
          object_type = kwargs['object_type'] if 'object_type' in kwargs else None
          binary_field = kwargs['binary_field'] if 'binary_field' in kwargs else None
          api_version = kwargs.get('version')
          external_id = kwargs['external_id'] if 'external_id' in kwargs else None
          return SObjectController(self, object_type, _id, binary_field, api_version, external_id)
  
      @commons.kwarg_adder
      def search(self, ss, **kwargs):
          """ Performs a search request.
  
            :param: ss: Search string. eg `'FIND {sfdc_py} RETURNING Account(Id, Name) LIMIT 5'`
            :type: ss: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
            :return: Search response
            :rtype: (dict, Search)
          """
  
          s = Search(self.session_id, self.instance_url, ss, **kwargs)
          req = s.request()
          return req, s
  
      @commons.kwarg_adder
      def execute_anonymous(self, ab, **kwargs):
          """ Performs an anonymous Apex execution request.
  
            :param: ab: Anonymous block of Apex code, eg: `'system.debug("Hello world")'`
            :type: ab: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
            :return: Execute anonymous response
            :rtype: (dict, ExecuteAnonymous)
          """
  
          ea = ExecuteAnonymous(
              self.session_id,
              self.instance_url,
              ab,
              **kwargs)
          req = ea.request()
          return req, ea
  
      @commons.kwarg_adder
      def approvals(self, body=None, **kwargs):
          """ Performs an approval process request.
  
            :param: body: Body of approval process request, if any.
            :type: body: dict
            :return: Approval response
            :rtype: (dict, ApprovalProcess)
          """
  
          k = {
              'request_body': body,
              'proxies': self.proxies
          }
          k.update(kwargs)
          ar = ApprovalProcess(
              session_id=self.session_id,
              instance_url=self.instance_url,
              **k)
          req = ar.request()
          return req, ar
  
      @commons.kwarg_adder
      def debug(self, **kwargs):
          """ Sets up debugging for the client at the level provided in the `level` kwarg.
  
          If this method is called but no `level` kwarg is provided, the client sets the debug level to `logging.INFO` by
          default.
  
          .. versionadded:: 1.0.0
  
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
  
          logger = self.logger
          if 'level' in kwargs:
              level = kwargs['level']
              logger.setLevel(level)
          else:
              logger.setLevel(logging.INFO)
  
      def __enter__(self):
          """
          Invoked on entry to this class, handle login automatically for context managers
  
          :return: self
          """
          self.login()
          return self
  
      def __exit__(self, _type, value, traceback):
          """
          Handle logout automatically upon exiting the statement's body
          https://docs.python.org/2/reference/datamodel.html#with-statement-context-managers
          :param type: exception type
          :param value: exception value
          :param traceback: traceback for the exception
          :return:None
          """
          try:
              self.logout()
          except Exception as e:
              self.logger.warning('Unable to logout. Reason: {}'.format(e.args[0]))
              self.logger.info('__exit__ params: (%s, %s, %s)' % (_type, value, traceback))
  
  
  class ExecuteAnonymous(commons.BaseRequest):
      """ Performs a request to `/services/data/vX.XX/tooling/executeAnonymous/`
  
          .. versionadded:: 1.0.0
      """
      def __init__(self, session_id, instance_url, ab, **kwargs):
          """ Constructor. Calls `super`, then encodes the `service` query string including the abstract block (`ab`)
          passed in.
  
            :param: session_id: Session ID used to make request
            :type: session_id: string
            :param: instance_url: Instance URL used to make the request (eg. `'eu11.salesforce.com'`)
            :type: instance_url: string
            :param: ab: Anonymous block of Apex code, eg: `'system.debug("Hello world")'`
            :type: ab: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
  
          super(
              ExecuteAnonymous,
              self).__init__(
              session_id,
              instance_url,
              **kwargs)
          exec_anon = urlencode(
              {'anonymousBody': ab.encode('utf-8')})
          self.service = TOOLING_ANONYMOUS % (self.api_version, exec_anon)
  
  
  class Login(commons.OAuthRequest):
      """ Performs a request to `'/services/oauth2/token'`
  
          .. versionadded:: 1.0.0
      """
      def __init__(
              self,
              username,
              password,
              client_id,
              client_secret,
              **kwargs):
          """ Constructor. Calls `super`, assigns all params to their equivalent instance variables, sets `http_method` to
          POST, and prepares the request service and payload.
  
            :param: username: Salesforce username
            :type: username: string
            :param: password: Salesforce password
            :type: password: string
            :param: login_url: Salesforce login URL
            :type: login_url: string
            :param: client_id: Salesforce client ID
            :type: client_id: string
            :param: client_secret: Salesforce client secret
            :type: client_secret: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
  
          super(Login, self).__init__(None, None, **kwargs)
          self.username = username
          self.password = password
          self.login_url = kwargs['login_url'] if 'login_url' in kwargs else 'login.salesforce.com'
          self.client_id = client_id
          self.client_secret = client_secret
          self.http_method = 'POST'
          self.service = '/services/oauth2/token'
          self.payload = self.get_payload()
  
      def get_payload(self):
          """ Returns the payload dict to be used in the request.
  
            :return: OAuth2 request body required to obtain access token.
            :rtype: dict
          """
  
          return {
              'grant_type': 'password',
              'client_id': self.client_id,
              'client_secret': self.client_secret,
              'username': self.username,
              'password': self.password
          }
  
      def request(self):
          """ Gets the result of `super` for this method, then assigns the `access_token` to `session_id`.
          Returns request response.
  
            :return: Response dict
            :rtype: dict
          """
          response = super(Login, self).request()
          if response is not None:
              if 'access_token' in response:
                  self.session_id = response['access_token']
              return response
  
      def get_session_id(self):
          """ Returns the session ID obtained if the login request was successful
  
            :return: Session ID
            :rtype: string
          """
  
          return self.session_id
  
  
  class LoginException(Exception):
      """ Exception thrown during due to login failure.
  
          .. versionadded:: 1.0.0
      """
      pass
  
  
  class Logout(commons.OAuthRequest):
      """ Performs a request to `'/services/oauth2/revoke'`
  
          .. versionadded:: 1.0.0
      """
      def __init__(self, session_id, instance_url, **kwargs):
          """ Constructor. Calls `super`, assigns the service from the hardcoded value, and sets a `payload`
          instance variable with a dict where key is `'token'` and value is `session_id`
  
            :param: session_id: Session ID used to make request
            :type: session_id: string
            :param: instance_url: Instance URL used to make the request (eg. `'eu11.salesforce.com'`)
            :type: instance_url: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
  
          super(Logout, self).__init__(session_id, instance_url, **kwargs)
          self.service = REVOKE_SERVICE
          self.payload = {'token': self.session_id}
  
  
  class Query(commons.BaseRequest):
      """ Performs a request to `'/services/data/vX.XX/query/'`
  
          .. versionadded:: 1.0.0
      """
      def __init__(self, session_id, instance_url, query_string, **kwargs):
          """ Constructor. Calls `super`, then encodes the `service` including the `query_string` provided
  
            :param: session_id: Session ID used to make request
            :type: session_id: string
            :param: instance_url: Instance URL used to make the request (eg. `'eu11.salesforce.com'`)
            :type: instance_url: string
            :param: query_string: Query string. eg `'SELECT Id FROM Account LIMIT 10'`
            :type: query_string: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
          super(Query, self).__init__(session_id, instance_url, **kwargs)
          qry = urlencode({'q': query_string.encode('utf-8')})
          self.service = QUERY_SERVICE % (self.api_version, qry)
  
  
  class QueryMore(commons.BaseRequest):
      """ Performs recursive requests to `'/services/data/vX.XX/query/'` when there are multiple batches to process.
  
          .. versionadded:: 1.0.0
      """
      def __init__(self, session_id, instance_url, query_string, **kwargs):
          """ Constructor. Calls `super`, then assigns the `query_string` to an instance variable.
  
            :param: session_id: Session ID used to make request
            :type: session_id: string
            :param: instance_url: Instance URL used to make the request (eg. `'eu11.salesforce.com'`)
            :type: instance_url: string
            :param: query_string: Query string. eg `'SELECT Id FROM Account LIMIT 10'`
            :type: query_string: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
          super(QueryMore, self).__init__(session_id, instance_url, **kwargs)
          self.query_string = query_string
  
      def request(self, *args):
          """ Makes a `Query` request for the initial query string, then calls itself recursively to request all remaining
          batches, if there are any.  This method will break the recursion and return all when the last batch processed
          contains a `done` value equal to `True`.
  
            :param: results: All queried batch results obtained in prior recursions of this method.
            :type: results: [dict]
            :return: A list of dicts where each dict is a batch of query results
            :rtype: [dict]
          """
  
          (last, results) = (
              dict(),
              list() if len(args) is 0 else args[0],
          )
  
          len_results = len(results)
  
          if len_results == 0:
              q = Query(self.session_id, self.instance_url, self.query_string)
              q.set_proxies(self.proxies)
              response = q.request()
              results.append(response)
              last = response
          elif len_results > 0:
              last = results[len_results - 1]
              if last.get('done') is False:
                  (headers, logger, request_object, response, service) = self.get_request_vars()
                  service = 'https://%s%s' % (self.instance_url, last.get('nextRecordsUrl'))
                  logging.getLogger('sfdc_py').info('%s %s' %
                                                    (self.http_method, service))
                  try:
                      request_object = requests.get(
                          service, headers=headers, proxies=self.proxies)
                      self.status = request_object.status_code
                      if request_object.content.decode('utf-8') == 'null':
                          raise commons.SFDCRequestException('Request body is null')
                      else:
                          last = request_object.json()
                  except Exception as e:
                      self.exceptions.append(e)
                      logger.error('%s %s %s' % (self.http_method, service, self.status))
                      logger.error(e.args[0])
                      return
                  else:
                      results.append(last)
  
          if last.get('done') is True:
              return results
          elif last.get('done') is False:
              return self.request(results)
  
  
  class Search(commons.BaseRequest):
      """ Performs a request to `'/services/data/vX.XX/search/'`
  
          .. versionadded:: 1.0.0
      """
      def __init__(self, session_id, instance_url, search_string, **kwargs):
          """ Constructor. Calls `super`, then encodes the `service` including the `search_string` provided
  
            :param: session_id: Session ID used to make request
            :type: session_id: string
            :param: instance_url: Instance URL used to make the request (eg. `'eu11.salesforce.com'`)
            :type: instance_url: string
            :param: search_string: Search string. eg `'FIND {sfdc_py} RETURNING Account(Id, Name) LIMIT 5'`
            :type: search_string: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
          super(Search, self).__init__(session_id, instance_url, **kwargs)
          s = urlencode({'q': search_string.encode('utf-8')})
          self.service = SEARCH_SERVICE % (self.api_version, s)
  
  
  class SObjectBlob(commons.BaseRequest):
      """ Perform a request to `'/services/data/vX.XX/sobjects'` where file i/o is necessary.
  
          .. versionadded:: 1.0.0
      """
  
      def __init__(self, _client, service, http_method):
          """ Constructor. Calls `super`, then sets `service` and `http_method` instance variables.
  
            :param: session_id: Session ID used to make request
            :type: session_id: string
            :param: instance_url: Instance URL used to make the request (eg. `'eu11.salesforce.com'`)
            :type: instance_url: string
            :param: service: The service to append to /services/data/vX.XX/sobjects
            :type: service: string
            :param: http_method: Method to use with request (`'GET'` and `'POST'` currently supported.)
            :type: http_method: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
  
          k = {'http_method': http_method}
          k.update(_client.client_kwargs)
          super(SObjectBlob, self).__init__(_client.session_id, _client.instance_url, **k)
          self.service = service
  
      def set_request_body(self, **kwargs):
          """ Creates binary request body by merging `entity`, `json_body`, `file_content_type`, `field`, `filename` and
          `content` from `**kwargs**` into a binary body template.  Sets `request_body` instance variable with the result.
  
          Note: `content` can be either a `file` or a raw value.
  
            :param: **kwargs:
            :type: **kwargs: string
          """
  
          if self.http_method == 'POST':
              content = kwargs['content']
              self.request_body = INSERT_BINARY_BODY_TEMPLATE % (
                  kwargs['entity'],
                  kwargs['json_body'],
                  kwargs['file_content_type'],
                  kwargs['field'],
                  kwargs['filename'],
                  content)
  
      def request(self):
          """ Returns the request response.
  
          :return: response
          :rtype: dict
          """
  
          (headers, logger, request_object, response, service) = self.get_request_vars()
          logging.getLogger('sfdc_py').info('%s %s' %
                                            (self.http_method, service))
          if self.http_method == 'GET':
              headers['Content-Type'] = 'application/octet-stream'
              try:
                  request_object = requests.get(
                      service, headers=headers, proxies=self.proxies, stream=True)
                  self.status = request_object.status_code
                  if request_object.content.decode('utf-8') == 'null':
                      raise commons.SFDCRequestException('Request body is null')
                  else:
                      self.response = response = request_object
              except Exception as e:
                  self.exceptions.append(e)
                  logger.error('%s %s %s' %
                               (self.http_method, service, self.status))
  
                  logger.error(e.args[0])
                  return
              finally:
                  return response
          elif self.http_method == 'POST':
              headers['Content-Type'] = 'multipart/form-data;boundary="boundary_string"'
              try:
                  request_object = requests.post(
                      service,
                      headers=headers,
                      proxies=self.proxies,
                      data=self.request_body)
                  self.status = request_object.status_code
                  if request_object.content.decode('utf-8') == 'null':
                      raise commons.SFDCRequestException('Request body is null')
                  else:
                      self.response = response = request_object.json()
              except Exception as e:
                  self.exceptions.append(e)
                  logger.error('%s %s %s' %
                               (self.http_method, service, self.status))
                  logger.error(e.args[0])
                  return
              finally:
                  return response
  
  
  class SObjectController(object):
      """ A special class that controls insert/update/delete/query/describe of SObject resources.
  
          .. versionadded:: 1.0.0
      """
      def __init__(self, _client, object_type, _id, binary_field, api_version, external_id):
          """ Constructor.
  
            :param: _client: Salesforce client object
            :type: _client: Client
            :param: object_type: Name of the SObject, eg. `'Case'`
            :type: object_type: string
            :param: _id: Resource ID, if available
            :type: _id: string
            :param: binary_field: Binary field name, if available on object, eg. `'Body'`
            :type: binary_field: string
          """
  
          self.__client__ = _client
          self.id = _id
          self.object_type = object_type
          self.binary_field = binary_field
          self.api_version = api_version
          self.external_id = external_id
          # Maintain client kwargs
          self.client_kwargs = _client.client_kwargs
  
      def get_service(self):
          """ Returns the correct sobject service depending on whether the countroller contains an `id` instance variable
  
          :return: service
          :rtype: string
          """
  
          if self.binary_field is not None and self.object_type is not None and self.id is None:
              return SOBJ_SERVICE % (
                  self.api_version, '/' + self.object_type)
          elif self.id is not None and self.object_type is not None and self.external_id is None:
              return '/%s/%s' % (self.object_type, self.id)
          elif self.id is not None and self.object_type is not None and self.external_id is not None:
              return '/%s/%s/%s' % (self.object_type, self.external_id, self.id)
          elif self.object_type is None:
              return ''
          return '/%s' % self.object_type
  
      @commons.kwarg_adder
      def insert(self, body, **kwargs):
          """ Creates an SObject in Salesforce.
  
          Note: if `binary_field` is defined in kwargs, an `SObjectBlob` request will be made and returned, otherwise an
          `SObject` request will be made.
  
            :param: body: Body of SObject request.
            :type: body: dict
            :param: **kwargs: kwargs
            :type: **kwargs: dict
            :return: Insert result from Salesforce
            :rtype: (dict, SObject|SObjectBlob)
          """
  
          sobj = None
          _client = self.__client__
          resource_id = self.get_service()
          if self.binary_field is None:
              k = {
                  'http_method': 'POST',
                  'request_body': body,
                  'resource_id': resource_id
              }
              k.update(kwargs)
              sobj = SObjects(_client, **k)
          elif self.binary_field is not None and 'binary' in kwargs:
              sobj = SObjectBlob(
                  _client,
                  resource_id,
                  'POST')
  
              # Prep request body properties
              entity = self.object_type.lower()
              json_body = json.dumps(body)
              field = self.binary_field
              filename = kwargs['binary'][0]
              content = kwargs['binary'][1]
              file_content_type = kwargs['binary'][2]
  
              # Set the request body
              sobj.set_request_body(
                  entity=entity,
                  json_body=json_body,
                  field=field,
                  filename=filename,
                  content=content,
                  file_content_type=file_content_type)
          req = sobj.request()
          return req, sobj
  
      @commons.kwarg_adder
      def update(self, body, **kwargs):
          """ Updates an SObject in Salesforce.
  
            :param: body: Body of SObject request.
            :type: body: dict
            :return: Update result from Salesforce
            :rtype: (None, SObject)
          """
  
          _client = self.__client__
          resource_id = self.get_service()
          k = {
              'http_method': 'PATCH',
              'request_body': body,
              'resource_id': resource_id
          }
          k.update(kwargs)
          sobj = SObjects(_client, **k)
          req = sobj.request()
          return req, sobj
  
      @commons.kwarg_adder
      def upsert(self, body, **kwargs):
          """ Upserts an SObject in Salesforce.
  
            :param: body: Body of SObject request.
            :type: body: dict
            :return: Upserts result from Salesforce
            :rtype: (None, SObject)
          """
  
          _client = self.__client__
          resource_id = self.get_service()
          k = {
              'http_method': 'PATCH',
              'request_body': body,
              'resource_id': resource_id
          }
          k.update(kwargs)
          sobj = SObjects(_client, **k)
          req = sobj.request()
          return req, sobj
  
      @commons.kwarg_adder
      def delete(self, **kwargs):
          """ Deletes an SObject in Salesforce.
  
            :return: Delete result from Salesforce
            :rtype: (None, SObject)
          """
  
          _client = self.__client__
          resource_id = self.get_service()
          k = {
              'http_method': 'DELETE',
              'resource_id': resource_id
          }
          k.update(kwargs)
          sobj = SObjects(_client, **k)
          req = sobj.request()
          return req, sobj
  
      @commons.kwarg_adder
      def query(self, **kwargs):
          """ Queries an SObject in Salesforce. If a `binary_field` instance variable is defined, this method will further
          query the binary field content and return it accordingly.
  
            :return: Query result from Salesforce
            :rtype: (dict, SObject)|(dict, SObject, SObjectBlob)
          """
          (_client, resource_id) = (self.__client__, self.get_service())
          k = {
              'http_method': 'GET',
              'resource_id': resource_id
          }
          k.update(kwargs)
          sobj = SObjects(_client, **k)
          req = sobj.request()
          if self.binary_field is not None and isinstance(
                  req, dict) and self.binary_field in req:
              bin_service = req[self.binary_field]
              sob_blob = SObjectBlob(
                  _client,
                  bin_service,
                  'GET')
              sob_blob.request()
              return req, sobj, sob_blob
          return req, sobj
  
      @commons.kwarg_adder
      def describe(self, **kwargs):
          """ Describes the metadata for an SObject in Salesforce.
  
          .. versionadded:: 1.0.0
  
            :return: Describe result from Salesforce
            :rtype: (dict, SObject)
          """
  
          _client = self.__client__
          resource_id = self.get_service()
          k = {
              'http_method': 'GET',
              'resource_id': resource_id + '/describe'
          }
          k.update(kwargs)
          sobj = SObjects(_client, **k)
          req = sobj.request()
          return req, sobj
  
      @commons.kwarg_adder
      def describe_global(self, **kwargs):
          """ Lists the available objects and their metadata for the organizations data.
  
          .. versionadded:: 1.0.0
  
            :return: Describe global result from Salesforce
            :rtype: (dict, SObject)
          """
  
          _client = self.__client__
          resource_id = self.get_service()
          k = {
              'http_method': 'GET',
              'resource_id': resource_id
          }
          k.update(kwargs)
          sobj = SObjects(_client, **k)
          req = sobj.request()
          return req, sobj
  
  
  class SObjects(commons.BaseRequest):
      """ Perform a request to `'/services/data/vX.XX/sobjects'`
  
          .. versionadded:: 1.0.0
      """
      def __init__(self, _client, **kwargs):
          """ Constructor. Calls `super`, retrieves `resource_id` from `**kwargs` if present, then creates `self.service`
          instance variable.
  
            :param: session_id: Session ID used to make request
            :type: session_id: string
            :param: instance_url: Instance URL used to make the request (eg. `'eu11.salesforce.com'`)
            :type: instance_url: string
            :param: **kwargs: kwargs
            :type: **kwargs: dict
          """
          super(SObjects, self).__init__(_client.session_id, _client.instance_url, **kwargs)
          resource_id = kwargs.get('resource_id')
          self.service = SOBJ_SERVICE % (self.api_version, resource_id)
  
      def request(self):
          """ Makes the appropriate request depending on the `http_method`.  Supported now are: `'GET'`, `'POST'`,
          `'PATCH'`, and `'DELETE'`. Returns request response.
  
          Note: As successful `'PATCH'` and `'DELETE'` responses return `NO CONTENT`, this method will return `None`.
          It may be advisable to check the `status` of the `SObject` instance returned as an additional factor in
          determining whether the request succeeded.
  
            :return: response dict
            :rtype: dict|None
          """
          sobjects_headers = {
              'Content-Type': 'application/json',
              'Accept-Encoding': 'application/json',
              'Sforce-Auto-Assign': 'FALSE'
          }
  
          (headers, logger, request_object, response, service) = (
              sobjects_headers,
              logging.getLogger('sfdc_py'),
              None,
              None,
              'https://%s%s' % (self.instance_url, self.service)
          )
          headers['Authorization'] = 'OAuth %s' % self.session_id
  
          logger.info('%s %s' % (self.http_method, service))
  
          if self.http_method == 'POST':
              request_object = requests.post(
                  service,
                  headers=headers,
                  json=self.request_body,
                  proxies=self.proxies)
          elif self.http_method == 'PATCH':
              request_object = requests.patch(
                  service,
                  headers=headers,
                  json=self.request_body,
                  proxies=self.proxies)
              self.status = request_object.status_code
              if request_object.status_code == requests.codes.no_content:
                  return None
          elif self.http_method == 'DELETE':
              request_object = requests.delete(
                  service, headers=headers, proxies=self.proxies)
              self.status = request_object.status_code
              if request_object.status_code == requests.codes.no_content:
                  return None
          elif self.http_method == 'GET':
              request_object = requests.get(
                  service, headers=headers, proxies=self.proxies)
  
          self.status = request_object.status_code
  
          try:
              if request_object.content.decode('utf-8') == 'null':
                  raise commons.SFDCRequestException('Request body is null')
              else:
                  response = request_object.json()
          except Exception as e:
              self.exceptions.append(e)
              logger.error('%s %s %s' % (self.http_method, service, self.status))
              logger.error(e.args[0])
              return
          finally:
              return response
  
  
  def client(username, password, client_id, client_secret, **kwargs):
      """ Builds a `Client` and returns it.
  
          .. versionadded:: 1.0.0
  
      Note: if any of the required parameters are missing, a `ValueError` will be raised.
  
          :Parameters:
              `*username` (`string`)
                Salesforce username.
              `*password` (`string`)
                Salesforce password.
              `*client_id` (`string`)
                Salesforce client ID.
              `*client_secret` (`string`)
                Salesforce client secret.
              `\**kwargs`
                kwargs (see below)
  
          :Keyword Arguments:
              `*login_url` (`string`)
                Salesforce login URL without protocol
  
                Default: `'login.salesforce.com'`
              `*protocol` (`string`)
                Protocol (future use)
              `*proxies` (`dict`)
                A dict containing proxies to be used by `requests` module.
  
                Example: `{"https": "example.org:443"}`
  
                Default: `None`
              `*timeout` ('string')
                Tell Requests to stop waiting for a response after a given number of seconds
  
          :returns: client
          :rtype: Client
          :raises: ValueError
      """
  
      if username is None:
          raise ValueError('`username` cannot be None')
      elif password is None:
          raise ValueError('`password` cannot be None')
      elif client_id is None:
          raise ValueError('`client_id` cannot be None')
      elif client_secret is None:
          raise ValueError('`client_secret` cannot be None')
      return Client(
          username,
          password,
          client_id,
          client_secret,
          **kwargs)
  