""" .. module:: commons :synopsis: Classes to be inherited within `sfdc` module. .. moduleauthor:: Jose Garcia Ponce <jgarciaponce@salesforce.com>, Aaron Caffrey <acaffrey@salesforce.com> .. versionadded:: 1.0.0 """ from __future__ import absolute_import import logging import requests DEFAULT_API_VERSION = '37.0' def delete_request(base_request): """ Performs DELETE request for the class provided. :param: base_request: Class with which to make request. :type: BaseRequest :return: response :rtype: requests.Response """ (headers, _, _, _, service) = base_request.get_request_vars() return requests.delete( service, headers=headers, proxies=base_request.proxies, timeout=base_request.timeout) def get_request(base_request): """ Performs GET request for the class provided. :param: base_request: Class with which to make request. :type: BaseRequest :return: response :rtype: requests.Response """ (headers, _, _, _, service) = base_request.get_request_vars() return requests.get( service, headers=headers, proxies=base_request.proxies, timeout=base_request.timeout) def patch_request(base_request): """ Performs PATCH request for the class provided. :param: base_request: Class with which to make request. :type: BaseRequest :return: response :rtype: requests.Response """ (headers, _, _, _, service) = base_request.get_request_vars() return requests.patch( service, headers=headers, proxies=base_request.proxies, timeout=base_request.timeout, json=base_request.request_body) def post_request(base_request): """ Performs POST request for the class provided. :param: base_request: Class with which to make request. :type: BaseRequest :return: response :rtype: requests.Response """ (headers, _, _, _, service) = base_request.get_request_vars() return requests.post( service, headers=headers, proxies=base_request.proxies, timeout=base_request.timeout, json=base_request.request_body) def put_request(base_request): """ Performs PUT request for the class provided. :param: base_request: Class with which to make request. :type: BaseRequest :return: response :rtype: requests.Response """ (headers, _, _, _, service) = base_request.get_request_vars() return requests.put( service, headers=headers, proxies=base_request.proxies, timeout=base_request.timeout, data=base_request.request_body) def kwarg_adder(func): """ Decorator to add the kwargs from the client to the kwargs at the function level. If the same parameters are used in both, the function level kwarg will supersede the one at the client level. :param func: client function to add client kwargs to :return: the function with updated kwargs """ def decorated(self, *args, **function_kwarg): if hasattr(self, 'client_kwargs'): client_args = {key: val for key, val in self.client_kwargs.items() if key not in function_kwarg.keys()} function_kwarg.update(client_args) return func(self, *args, **function_kwarg) return decorated class SFDCRequestException(Exception): """ This exception is raised when we fail to complete requests to the # noqa `SFDC REST API <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_what_is_rest_api.htm>`_.# noqa .. versionadded:: 1.0.0 """ pass class ApiNamespace(object): """ Base class for API namespaces. .. versionadded:: 1.0.0 """ def __init__(self, client): self.client = client self.client_kwargs = client.client_kwargs class BaseRequest(object): """ Base class for all request objects, for convenience, new request types should inherit from this class. .. versionadded:: 1.0.0 """ def __init__(self, session_id, instance_url, **kwargs): """ Constructor. :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 :Keyword Arguments: * *api_version* (`string`) -- API version for the request Default: `'37.0'` * *http_method* (`string`) -- HTTP method for the request Default: `'GET'` * *proxies* (`dict`) -- A dict containing proxies to be used by `requests` module. Ex: `{"https": "example.org:443"}` Default: `None` * *timeout* (`string`) -- A dict indicating the timeout value for connect and read to be used by `requests` module. Ex: `{"timeout": "30"}` Default: `None` * *request_body* (`dict`) -- A dict containing the request body Default: `None` """ self.proxies = kwargs.get('proxies', None) self.session_id = session_id self.http_method = kwargs.get('http_method', 'GET') self.instance_url = instance_url self.request_body = kwargs.get('request_body', None) self.api_version = kwargs.get('version', DEFAULT_API_VERSION) self.timeout = float(kwargs['timeout']) if 'timeout' in kwargs else None self.service = None self.status = None self.response = None self.headers = None self.request_url = None self.exceptions = [] def get_request_url(self): """ Returns the request URL. (default: `'https://<instance_url><service>'`) :return: request_url :rtype: string """ self.request_url = 'https://%s%s' % (self.instance_url, self.service) if self.request_url is None else self.request_url return self.request_url def get_headers(self): """ Returns headers dict for the request. :return: headers :rtype: dict """ self.headers = { 'Content-Type': 'application/json', 'Accept-Encoding': 'application/json', 'Authorization': 'OAuth %s' % self.session_id} if self.headers is None else self.headers return self.headers def get_request_vars(self): """ Returns the variables required by `request()` and other functions. :return: (headers, logger, request_object, response, service) :rtype: (dict, logging.Logger, requests.Request|None, list|dict|None, string) """ return ( self.get_headers(), logging.getLogger('sfdc_py'), None, None, self.get_request_url() ) def request(self): """ Makes request to Salesforce and returns serialised response. Catches any exceptions and appends them to `self.exceptions`. :return: response: Salesforce response, if available :rtype: list|dict|None """ (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 == 'POST': request_fn = post_request elif self.http_method == 'PUT': request_fn = put_request elif self.http_method == 'PATCH': request_fn = patch_request elif self.http_method == 'DELETE': request_fn = delete_request else: request_fn = get_request try: request_object = request_fn(self) self.status = request_object.status_code if request_object.content.decode('utf-8') == 'null': raise 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.message) return finally: return response def set_proxies(self, proxies): """ Sets `proxies` for this class. :param proxies: A dict containing proxies to use (see: # noqa `Proxies <http://docs.python-requests.org/en/master/user/advanced/#proxies)>`_ # noqa in the python-requests.org guide. :type: dict """ self.proxies = proxies class OAuthRequest(BaseRequest): """ Base class for all OAuth request objects .. versionadded:: 1.0.0 """ def __init__(self, session_id, instance_url, **kwargs): super(OAuthRequest, self).__init__(session_id, instance_url, **kwargs) self.headers = {'Content-Type': 'application/x-www-form-urlencoded'} self.login_url = None self.payload = None def request(self): (headers, logger, request_object, response, service) = self.get_request_vars() payload = self.payload logging.getLogger('sfdc_py').info('%s %s' % ('POST', service)) try: request_object = requests.post( service, headers=headers, data=payload, proxies=self.proxies, timeout=self.timeout) self.status = request_object.status_code if self.status == requests.codes.ok: response = request_object.json() else: ex = SFDCRequestException('OAuth call failed. Received %s status code' % self.status) ex.oauth_response = request_object.json() raise ex except Exception as e: self.exceptions.append(e) logger.error('%s %s %s' % (self.http_method, service, self.status)) logger.error(e.message) return finally: return response def get_request_url(self): url = self.instance_url if self.login_url is None else self.login_url self.request_url = 'https://%s%s' % ( url, self.service) if self.request_url is None else self.request_url return self.request_url