Search code examples
pythonapipython-requestshttp-postparameter-passing

Python: POST request to API failed


I am trying to send a POST request to Walmart API to request reports. But it is returning me 'Report request failed.' message with json. What am I doing wrong? I don't really have that much experience with API calls in python. Here is my code.

I have a class called "Walmart" to interact with the Walmart Marketplace API. It has methods to authenticate with the API, send requests and handle responses.

class Walmart(object):

    def __init__(self, client_id, client_secret):
        """To get client_id and client_secret for your Walmart Marketplace
        visit: https://developer.walmart.com/#/generateKey
        """
        self.client_id = client_id
        self.client_secret = client_secret
        self.token = None
        self.token_expires_in = None
        self.base_url = "https://marketplace.walmartapis.com/v3"

        session = requests.Session()
        session.headers.update({
            "WM_SVC.NAME": "Walmart Marketplace",
            "WM_QOS.CORRELATION_ID": uuid.uuid4().hex,
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "application/json",
        })
        session.auth = HTTPBasicAuth(self.client_id, self.client_secret)
        self.session = session

        # Get the token required for API requests
        self.authenticate()

    def authenticate(self):
        data = self.send_request(
            "POST", "{}/token".format(self.base_url),
            body={
                "grant_type": "client_credentials",
            },
        )
        self.token = data["access_token"]
        self.token_expires_in = data["expires_in"]

        self.session.headers["WM_SEC.ACCESS_TOKEN"] = self.token

    @property
    def report(self):
        return Report(connection=self)

    def send_request(
        self, method, url, params=None, body=None, json=None,
        request_headers=None
    ):
        # A unique ID which identifies each API call and used to track
        # and debug issues; use a random generated GUID for this ID
        headers = {
            "WM_QOS.CORRELATION_ID": uuid.uuid4().hex,
            "WM_SVC.NAME": "Walmart Marketplace",
            "Content-Type": "application/x-www-form-urlencoded",
            "Accept": "application/json",
        }
        if request_headers:
            headers.update(request_headers)

        response = None
        if method == "GET":
            response = self.session.get(url, params=params, headers=headers)
        elif method == "PUT":
            response = self.session.put(
                url, params=params, headers=headers, data=body
            )
        elif method == "POST":
            request_params = {
                "params": params,
                "headers": headers,
            }
            if json is not None:
                request_params["json"] = json
            else:
                request_params["data"] = body
            response = self.session.post(url, **request_params)

        if response is not None:
            try:
                response.raise_for_status()
            except requests.exceptions.HTTPError:
                if response.status_code == 401:
                    raise WalmartAuthenticationError((
                        "Invalid client_id or client_secret. Please verify "
                        "your credentials from https://developer.walmart."
                        "com/#/generateKey"
                    ))
                elif response.status_code == 400:
                    data = response.json()
                    if "error" in data and data["error"][0]["code"] == \
                            "INVALID_TOKEN.GMP_GATEWAY_API":
                        # Refresh the token as the current token has expired
                        self.authenticate()
                        return self.send_request(
                            method, url, params, body, request_headers
                        )
                raise
        try:
            return response.json()
        except ValueError:
            # In case of reports, there is no JSON response, so return the
            # content instead which contains the actual report
            return response.content

And here goes authentication and the request itself. I think I am doing it wrong with the send_request method, should I do it in a different way?

api_key = '<key>'
api_secret='<secret>'
wm = Walmart(api_key, api_secret)
wm.authenticate()

url = "https://marketplace.walmartapis.com/v3/reports/reportRequests"
headers = {
      "WM_QOS.CORRELATION_ID": uuid.uuid4().hex,
       "WM_SVC.NAME": "Walmart Marketplace",
       "Content-Type": "application/x-www-form-urlencoded",
       "Accept": "application/json",
}
data= {
        "reportType": "ITEM_PERFORMANCE",
        "reportVersion": "v1",
}
method="POST"

response_dict = wm.send_request(method, url, request_headers=headers, params=data)


if 'status_code' in response_dict and response_dict['status_code'] == 200:
    response_json = response_dict.get('json')
    request_id = response_json.get('requestId')
    print(f'Report request submitted. Request ID: {request_id}')
else:
    print('Report request failed.')
    if 'json' in response_dict:
        print(response_dict['json'])
    else:
        print(response_dict)

The response that I got was in the following form.

Report request failed. {'requestId': '46a864e8-80e8-4019-86f0-d7a1575349a4', 'requestStatus': 'RECEIVED', 'requestSubmissionDate': '2023-02-15T18:55:03Z', 'reportType': 'ITEM_PERFORMANCE', 'reportVersion': 'v1'}

Any help is appreciated


Solution

  • The response you get seems to be a successful response, as the property requestStatus is RECEIVED, and not ERROR (which is a possible value according to the API docs you linked).

    So, the issue is probably with your response checks.

    Based on your checks for response:

    if 'status_code' in response_dict and response_dict['status_code'] == 200:
        response_json = response_dict.get('json')
        request_id = response_json.get('requestId')
        print(f'Report request submitted. Request ID: {request_id}')
    else:
        print('Report request failed.')
        if 'json' in response_dict:
            print(response_dict['json'])
        else:
            print(response_dict)
    

    either 'status_code' in response_dict or response_dict['status_code'] == 200 is false, what makes the else block to be executed. I recommend you to print(response_dict) before the if-else block, to see the whole content and see which of those 2 conditions is false and handle it accordingly.

    I think that the issue is that the object you get from wm.send_request() does not contain status_code, since you get the content of the response (when you return response.json()) and not the session.Response object from the requests lib (see the docs).

    UPDATE:

    To fix your code, you need to check the response content, which is what the send_request() method returns. Also note that send_request() already does a lot of request/response error checking and raises exceptions if necessary

    api_key = '<key>'
    api_secret='<secret>'
    wm = Walmart(api_key, api_secret)
    wm.authenticate()
    
    url = "https://marketplace.walmartapis.com/v3/reports/reportRequests"
    headers = {
          "WM_QOS.CORRELATION_ID": uuid.uuid4().hex,
           "WM_SVC.NAME": "Walmart Marketplace",
           "Content-Type": "application/x-www-form-urlencoded",
           "Accept": "application/json",
    }
    data= {
            "reportType": "ITEM_PERFORMANCE",
            "reportVersion": "v1",
    }
    method="POST"
    
    # send_request() already checks for request/response errors and
    # raises exceptions if they happen, so we don't need to check for
    # them here. Unless we want to gracefully handle errors
    response_dict = wm.send_request(method, url, request_headers=headers, params=data)
    
    # Check if there is response and requestStatus value is not ERROR
    if response_dict and response_dict.get('requestStatus') != 'ERROR':
        request_id = response_dict.get('requestId')
        print(f'Report request submitted. Request ID: {request_id}')
    else:
        print('Report request failed.')
        print(response_dict)