Exemplo n.º 1
0
    def sobject_collections_request(self, method, records, all_or_none=True):
        # pylint:disable=too-many-locals
        assert method in ('GET', 'POST', 'PATCH', 'DELETE')
        if method == 'DELETE':
            params = dict(ids=','.join(records), allOrNone=str(bool(all_or_none)).lower())
            resp = self.handle_api_exceptions(method, 'composite/sobjects', params=params)
        else:
            if method in ('POST', 'PATCH'):
                records = [merge_dict(x, attributes={'type': x['type_']}) for x in records]
                for x in records:
                    x.pop('type_')
                post_data = {'records': records, 'allOrNone': all_or_none}
            else:
                raise NotSupportedError("Method {} not implemended".format(method))

            resp = self.handle_api_exceptions(method, 'composite/sobjects', json=post_data)
        resp_data = resp.json()

        x_ok, x_err, x_roll = self._group_results(resp_data, records, all_or_none)
        is_ok = not x_err
        if is_ok:
            return [x['id'] for i, x in x_ok]  # for .lastrowid

        width_type = max(len(type_) for i, errs, type_, id_ in x_err)
        width_type = max(width_type, len('sobject'))
        messages = ['', 'index {} sobject{:{width}s}error_info'.format(
            ('ID' + 16 * ' ' if x_err[0][3] else ''), '', width=(width_type + 2 - len('sobject')))]
        for i, errs, type_, id_ in x_err:
            field_info = 'FIELDS: {}'.format(errs[0]['fields']) if errs[0].get('fields') else ''
            msg = '{:5d} {} {:{width_type}s}  {}: {} {}'.format(
                i, id_ or '', type_, errs[0]['statusCode'], errs[0]['message'], field_info,
                width_type=width_type)
            messages.append(msg)
        raise SalesforceError(messages)
Exemplo n.º 2
0
    def handle_api_exceptions_inter(self, method: str, *url_parts: str,
                                    **kwargs: Any) -> requests.Response:
        """The main (middle) part - it is enough if no error occurs."""
        global request_count  # used only in single thread tests - OK # pylint:disable=global-statement
        # log.info("request %s %s", method, '/'.join(url_parts))
        api_ver = kwargs.pop('api_ver', None)
        url = self.rest_api_url(*url_parts, api_ver=api_ver)
        # The 'verify' option is about verifying TLS certificates
        kwargs_in = {
            'timeout': getattr(settings, 'SALESFORCE_QUERY_TIMEOUT', (4, 15)),
            'verify': True
        }
        kwargs_in.update(kwargs)
        log.debug('Request API URL: %s', url)
        request_count += 1
        session = self.sf_session

        try:
            time_statistics.update_callback(url, self.ping_connection)
            response = session.request(method, url, **kwargs_in)
        except requests.exceptions.Timeout:
            raise SalesforceError("Timeout, URL=%s" % url)
        if (response.status_code == 401  # Unauthorized
                and 'json' in response.headers['content-type']
                and response.json()[0]['errorCode'] == 'INVALID_SESSION_ID'):
            # Reauthenticate and retry (expired or invalid session ID or OAuth)
            token = session.auth.reauthenticate()
            if token:
                if 'headers' in kwargs:
                    kwargs['headers'].update(Authorization='OAuth %s' % token)
                try:
                    response = session.request(method, url, **kwargs_in)
                except requests.exceptions.Timeout:
                    raise SalesforceError("Timeout, URL=%s" % url)

        if response.status_code < 400:  # OK
            # 200 "OK" (GET, POST)
            # 201 "Created" (POST)
            # 204 "No Content" (DELETE)
            # 300 ambiguous items for external ID.
            # 304 "Not Modified" (after conditional HEADER request for metadata),
            return response
        # status codes docs (400, 403, 404, 405, 415, 500)
        # https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/errorcodes.htm
        self.raise_errors(response)
        return  # type: ignore[return-value]  # noqa
Exemplo n.º 3
0
    def raise_errors(self, response):
        """The innermost part - report errors by exceptions"""
        # Errors: 400, 403 permissions or REQUEST_LIMIT_EXCEEDED, 404, 405, 415, 500)
        # TODO extract a case ID for Salesforce support from code 500 messages

        # TODO disabled 'debug_verbs' temporarily, after writing better default messages
        verb = self.debug_verbs  # NOQA pylint:disable=unused-variable
        method = response.request.method
        data = None
        is_json = 'json' in response.headers.get('Content-Type',
                                                 '') and response.text
        if is_json:
            data = json.loads(response.text)
        if not (isinstance(data, list) and data and 'errorCode' in data[0]):
            messages = [response.text] if is_json else []
            raise OperationalError([
                'HTTP error "%d %s":' % (response.status_code, response.reason)
            ] + messages, response, ['method+url'])

        # Other Errors are reported in the json body
        err_msg = data[0]['message']
        err_code = data[0]['errorCode']
        if response.status_code == 404:  # ResourceNotFound
            if method == 'DELETE' and err_code in (
                    'ENTITY_IS_DELETED', 'INVALID_CROSS_REFERENCE_KEY'):
                # It was a delete command and the object is in trash bin or it is
                # completely deleted or it could be a valid Id for this sobject type.
                # Then we accept it with a warning, similarly to delete by a classic database query:
                # DELETE FROM xy WHERE id = 'something_deleted_yet'
                warn_sf([err_msg, "Object is deleted before delete or update"],
                        response, ['method+url'])
                # TODO add a warning and add it to messages
                return None
        if err_code in (
                'NOT_FOUND',  # 404 e.g. invalid object type in url path or url query?q=select ...
                'METHOD_NOT_ALLOWED',  # 405 e.g. patch instead of post
        ):  # both need to report the url
            raise SalesforceError([err_msg], response, ['method+url'])
        # it is good e.g for these errorCode: ('INVALID_FIELD', 'MALFORMED_QUERY', 'INVALID_FIELD_FOR_INSERT_UPDATE')
        raise SalesforceError([err_msg], response)