nish
nish

Reputation: 7280

How to validate json/xml body of a POST request in python

I am trying t write a web controller for an API with GET, POST, PUT functionalities. 1. In the method that handles the requests , especially POST and PUT, I want to validate if the required keys/fields are present in the request body. 2. I also want to check the authorization key present in the request header and throw unauthorized error etc. as response.

Is there an elegant way of doing this in python, write multiple if...else doesn't look elegant.

I have the following code which handles the request body:

from werkzeug.wrappers import BaseResponse as Response
.
.
.
    root = ET.fromstring(data)
            for child in root:
                order_completed_date = child.find('Order_Completed_Date')
                if order_completed_date is None:
                    #return json.loads({"status":"400", "message":"Order_Completed_Date is missing"})
                    return Response('Bad Request, Order_Complete_At missing', status=400)
                else:
                    order_completed_date = order_completed_date.text
                order_id = child.find('Order_Number')
                if order_id is None:
                    return Response('Bad Request, Order_Number missing', status=400) 
                else:
                    order_id = order_id.text
                product_id =child.find('SKU')
                if product_id is None:
                    return Response("Bad request, SKU is missing", status=400)
                else:
                    product_id = product_id.text
                .
                .
                .

So on, I'm writing if else for each field

Upvotes: 0

Views: 2947

Answers (1)

abarnert
abarnert

Reputation: 365925

We've got some code, so we can look at how to refactor it.

You essentially have minor variations on this code over and over:

order_completed_date = child.find('Order_Completed_Date')
if order_completed_date is None:
    #return json.loads({"status":"400", "message":"Order_Completed_Date is missing"})
    return Response('Bad Request, Order_Complete_At missing', status=400)
else:
    order_completed_date = order_completed_date.text

So, how could we turn that into a function?

First, just turn it into a function and see what's wrong with it:

def find_key():
    order_completed_date = child.find('Order_Completed_Date')
    if order_completed_date is None:
        return Response('Bad Request, Order_Complete_At missing', status=400)
    else:
        order_completed_date = order_completed_date.text

So, the first problem is that child value. It's not a constant; it's different each time through the loop, so we need to take that as a parameter.*

* Well, we could define find_key locally, and get child as a closure variable, but let's keep it simple for now.

Similarly, while 'Order_Completed_Date' isn't different each time through the loop, it is different for each different key, so we need to take that as well.*

* There's also an 'Order_Complete_At' string, but that seems to be a typo for 'Order_Completed_Date'. If it isn't, then you'll need to add another parameter, like key_error_name, that you can use for that.

The variable name order_completed_date doesn't have to change, because local variable names don't mean anything to Python—but it's obviously misleading to a human reader if we use it as a generic name for "the value for each key".

Finally, the big problem is what we return. We have two different kinds of things we could return—a node's text, or an error Response. We could return the former and raise an exception with the latter. Or return a pair of things, a text-or-error and a flag that tells us which is which. Or we could return a text and an error, one of which is None. The exception seems like the most complicated, but it automatically gives us a "non-local return", a way to break out of the rest of the function without having to check each return value one by one.

So:

def find_key(child, key):
    value = child.find(key)
    if value is None:
        raise KeyError(key)
    else:
        return value.text

And now:

try:
    for child in root:
        order_completed_date = find_key(child, 'Order_Completed_Date')
        order_id = find_key(child, 'Order_Number')
        product_id = find_key(child, 'SKU')
        # ...
except KeyError as e:
    return Response("Bad request, {} is missing".format(e.args[0]), status=400)

Upvotes: 2

Related Questions