Python Flask JSON KeyError - python

I'm trying to get some JSON data from another API service and update my flask app's database while user can download some PDF files. That API have 3 keys. 1st one is 'Status'. When that 'Status' key has "success" value, it also have other two keys and values too. Then app works fine without errors.
But when the 'Status' has the 'fail' value, other two keys and values won't be there. I wrote a some exception but it doesn't work and end up with a KeyError, KeyError: 'country'
Here is my code.
#app.route("/pdf/download/<int:pdf_id>", methods=['GET', 'POST'])
def downloads(pdf_id):
current_ip = someIPaddress
req = requests.request("GET", 'http://anotherwebsite.com/json/someIPaddress?fields=169')
req_two = req.json()
status = req_two['status']
country = req_two['country']
city = req_two['city']
download_file = Pdf_info.query.get(pdf_id)
if Ipaddress.query.filter(Ipaddress.ip_address == current_ip, Ipaddress.pdfid == pdf_id).first():
try:
return send_from_directory("pdfs/pdf/", filename=download_file.file_location_name, as_attachment=True)
except FileNotFoundError:
abort(404)
else:
if status == "success":
ip_adding = Ipaddress(ip_address=current_ip, pdfid=pdf_id, downtime=datetime.utcnow(), country=country, location=city)
db.session.add(ip_adding)
db.session.commit()
try:
return send_from_directory("pdfs/pdf/", filename=download_file.file_location_name, as_attachment=True)
except FileNotFoundError:
abort(404)
else:
ip_adding = Ipaddress(ip_address=current_ip, pdfid=pdf_id, downtime=datetime.utcnow())
db.session.add(ip_adding)
db.session.commit()
try:
return send_from_directory("pdfs/pdf/", filename=download_file.file_location_name, as_attachment=True)
except FileNotFoundError:
abort(404)
Can someone explain why this doesn't work or mention a solution please ?.

You are trying to fetch:
country = req_two['country']
city = req_two['city']
before you have tested the output of:
status = req_two['status']
so if status is fail then country= and city= will fail.
Use:
country = req_two.get('country')
city = req_two.get('city')
That will return None if the key is not found instead of a ``KeyError. It also allows you test the countryandcity` variables afterwards.

Related

Handling JSON registries with MongoDB

I have created a Flask service and I'm having a trouble on parsing some JSON values which are stored in MongoDB. I know how to parse every object which has a single value expect the address parameter which has more values inside (e.g "address":[{"street":"Jardine Place","city":"Lowgap","postcode":...)
JSON file:
{"name":"Morton Fitzgerald","email":"mortonfitzgerald#ontagene.com","yearOfBirth":{"$numberInt":"1997"},"address":[{"street":"Jardine Place","city":"Lowgap","postcode":{"$numberInt":"18330"}}]}
{"name":"Dorthy Cobb","email":"dorthycobb#ontagene.com","yearOfBirth":{"$numberInt":"1994"}}
In the python script, I'm trying to find a student based on the email, which I'm giving to the body at Postman. After that, if a student with an address is found I need to return a message with every value inside the "address" except the "city". With the following code, I'm taking some results but from them, I need to print out only the "street" and "postcode".
Python function:
#app.route('/getStudentAddress', methods=['GET'])
def get_student_address():
# Request JSON data
data = None
try:
data = json.loads(request.data)
uuid = request.headers.get('authorization')
except Exception as e:
return Response("bad json content",status=500,mimetype='application/json')
if data == None:
return Response("bad request",status=500,mimetype='application/json')
if not "email" in data:
return Response("Information incomplete",status=500,mimetype="application/json")
if (is_session_valid(uuid)):
student = students.find_one({"email":data['email']})
if(student != None):
student = students.find_one({"address":student['address']})
if(student != None):
student = {'name':student["name"],'address':student["address"]}
return Response(json.dumps(student), status=200, mimetype='application/json')
else:
return Response("Not-found", status=400, mimetype='application/json')
else:
return Response("Not authenticated user", status=401, mimetype='application/json')
Results:
{"name": "Morton Fitzgerald", "address": [{"street": "Jardine Place", "city": "Lowgap", "postcode": 18330}]}
Error:
If I choose a registry like {"name":"Dorthy Cobb","email":"dorthycobb#ontagene.com","yearOfBirth":{"$numberInt":"1994"}} which doesn't have an address I'm taking an error of KeyError: 'address'
Any thoughts?
You might consider adding a "try to fetch" function that checks to see if the key exists and provides a default value:
def tryfetch( container, key, default=None ):
return container[key] if key in container else default
That way, you can write things like::
address = tryfetch( student, "address" )
if address:
addr = tryfetch(address[0], "street", "NA") + tryfetch(address[0], "postcode", "NA")

Using Flask API, how to upload & download file from server using jwt_required decorator from browser/ client?

I suspect it has something got to do with refresh token. Could not understand how to use it by the docs. Can I know the exact code how to use it?
The access token is created during login:
#app.route('/login', methods=['POST','GET'])
def login():
username = request.form["email"]
password = request.form["password"]
my_token_expiry_time = datetime.timedelta(seconds=60)
segments = 0
access_token = None
if request.method == 'POST':
result_set = authenticate_user(username, password)
if result_set:
ss1 = select([nsettings]).\
where(nsettings.c.mattribute == 'my_jwt_expiry_time_min')
rss1 = g.conn.execute(ss1)
if rss1.rowcount > 0:
for r in rss1:
my_token_expiry_time = datetime.timedelta(seconds=
(int(r[nsettings.c.mvalue])* 60))
else:
my_token_expiry_time = datetime.timedelta(
seconds=(2 * 60 *60)) # 2 hours
#print result_set, 'result_set result_set'
session['email'] = result_set['email']
access_token = create_access_token(
identity=username, expires_delta=my_token_expiry_time)
user_dict = result_set
if user_dict:
session['email'] = user_dict['email']
session['id'] = result_set['id']
# users and related views
session['access_token'] = access_token
print access_token, 'aaaaaaaaaaa'
return jsonify({
'email': session['email'],
'user_id': result_set['id'],
'access_token': access_token,
'id': session['id'],
}), 200
else:
return jsonify({'message': "Invalid credentials, retry"}), 401
return "True"
The flask api call to upload:
#app.route('/rt/api/v1.0/issues/<int:issue_id>/documents', methods=['POST'])
#jwt_required
def rt_doc_upload(issue_id):
'''
Upload documents for a rt ticket.
'''
# Iterate through the list of files, we don't care about the
# attribute name. We consider only the first file and ignore the
# rest.
if 'id' in session:
uploader = "3"
minternal_only = True
bool_internal_update = False
msg_str = None
for attr, document in request.files.iteritems():
trans = g.conn.begin()
try:
orig_filename = document.filename
filename, upload_folder = check_or_insert_document(
orig_filename, uploader)
new_doc = add_doc(orig_filename, filename)
print orig_filename, 'origooooo'
ins = archival_docs.insert().values(new_doc)
rs = g.conn.execute(ins)
doc_id = rs.inserted_primary_key[0]
filename = (str(doc_id) + '_' + orig_filename)
stmt = archival_docs.update().values(stored_name=filename).\
where(archival_docs.c.id == doc_id)
g.conn.execute(stmt)
document.save(os.path.join(upload_folder, filename))
mattach_doc_id = genUrl(doc_id)
trans.commit()
return jsonify(
{'issue_doc_id': rs.inserted_primary_key[0]}), 201
except Exception, e:
print e
trans.rollback()
return jsonify({'message': "Did not find any file"}), 400
return jsonify({'message': "UNAUTHORIZED"}), 401
When used with runserver and on commenting the jwt_required decorator I am able to upload and download files
Using sqlalchemy core, python and flask. The api call to upload worked for more than a month, but suddenly stopped working now

Testing content of Django.contrib.messages for invalid forms

I'm trying to test the content of messages while processing ModelForms with Django. I've got the following View (assume there's a Thing model with a required name field):
#login_required
def update(request, thing_id):
thing = Thing.objects.get(id=thing_id) # assume this works
if request.method == "POST":
form = ThingModelForm(request.POST, instance=thing)
if form.is_valid():
form.save()
messages.success(request, "Success!")
return redirect("/wherever")
else:
messages.error(request, "Oops!")
else:
form = ThingModelForm(instance=thing)
args = ("myapp/update.html", {"form": form})
kwargs = {"context_instance": RequestContext(request)}
return render_to_response(*args, **kwargs)
Now, I've got two unit tests. The first tests valid data, while the second tests invalid data. (Note that client login happens during setUp):
def test_update_account(self):
url = reverse('update', args=[1]) # assume that's a valid id
resp = self.client.post(url, {"name": "foo"})
self.assertEqual(resp.status_code, 302)
m = resp.cookies.get('messages', '')
self.assertTrue("Success!" in m.output())
And now to test invalid data:
def test_update_account_failure(self):
url = reverse('update', args=[1]) # assume that's a valid id
resp = self.client.post(url, {"name": ""}) # name is required
self.assertEqual(resp.status_code, 200)
# This works:
self.assertTrue("Oops!" in resp.content)
# This fails:
m = resp.cookies.get('messages', '')
self.assertTrue("Oops!" in m.output())
Why would accessing the message's content through the cookie work in one instance but fail in another?
Two things you could check:
When you create the request self.client.post(url, {"name": ""}) is a Thing instance returned here: thing = Thing.objects.get(id=thing_id) If not it will not reach the line of code where you set your error message: messages.error(request, "Oops!") as Thing.objects.get will throw an error.
If there are no results that match the query, get() will raise a
DoesNotExist exception.
If the first thing does return a Thing instance, you could check whether a redirect return redirect("/wherever") after setting the error message messages.error(request, "Oops!") does change anything.

HTTP POST with XML data does not work in Django-Piston

I have implemented API with django piston in which its take data from sms/mms . For MMS case i have to post XML data with image and others . Here is my code snippet on handlers.py
def create(self, request,*args,**kwagrs):
try:
file_type = None
raw_data = request.raw_post_data
data = serializers.deserialize("xml", raw_data)
try:
parser = Parse(data.stream.getvalue())
message = parser.get_message()
action_id = parser.get_action_id()
except Exception,e:
return HttpResponse(Response({'sender':parser.get_sender(),'error_description':str(e)}).get_error_response(), mimetype='text/xml')
if action_id in ['o','m','vt','vh','yritys']:
return self.post_message(request,parser)
elif action_id == 'poista' or action_id == 'lopeta':
return self.expired_message(request,parser)
elif action_id == 'tiedot':
return self.get_contact_info(request,parser)
except Exception,e:
ad_id = None
return HttpResponse(Response({'sender':parser.get_sender(),'error_description':str(e)}).get_error_response(), mimetype='text/xml')
when I am posting xml data with CURL its working , but when i use Firefox, httprequester its throwing me "BAD REQUEST"
Check this:
I get a 400 Bad Request error while using django-piston
Create middleware as:
class ContentTypeMiddleware(object):
def process_request(self, request):
if 'charset=UTF-8' in request.META['CONTENT_TYPE']:
request.META['CONTENT_TYPE'] = request.META['CONTENT_TYPE'].replace('; charset=UTF-8','')
return None
Add it in settings:
MIDDLEWARE_CLASSES = (
'app.middleware.ContentTypeMiddleware',
)
Try hurl.it for API testing. Check your post data. Set your header info if required.

Python input validation and edge case handling

Is there a better pattern for input validation than I'm using in this function?
https://github.com/nathancahill/clearbit-intercom/blob/133e4df0cfd1a146cedb3c749fc1b4fac85a6e1b/server.py#L71
Here's the same function without any validation. It's much more readable, it's short and to the point (9 LoC vs 53 LoC).
def webhook(clearbitkey, appid, intercomkey):
event = request.get_json()
id = event['data']['item']['id']
email = event['data']['item']['email']
person = requests.get(CLEARBIT_USER_ENDPOINT.format(email=email), auth=(clearbitkey, '')).json()
domain = person['employment']['domain']
company = requests.get(CLEARBIT_COMPANY_ENDPOINT.format(domain=domain), auth=(clearbitkey, '')).json()
note = create_note(person, company)
res = requests.post(INTERCOM_ENDPOINT,
json=dict(user=dict(id=id), body=note),
headers=dict(accept='application/json'),
auth=(appid, intercomkey))
return jsonify(note=res.json())
However, it doesn't handle any of these errors:
dict KeyError's (especially nested dicts)
HTTP errors
Invalid JSON
Unexpected responses
Is there a better pattern to follow? I looked into using a data validation library like voluptous but it seems like I'd still have the same problem of verbosity.
Your original code on github seems fine to me. It's a little over complicated, but also handle all cases of error. You can try to improve readability by abstract things.
Just for demonstration, I may write code like this:
class ValidationError(Exception):
"Raises when data validation fails"
pass
class CallExternalApiError(Exception):
"Raises when calling external api fails"
pass
def get_user_from_event(event):
"""Get user profile from event
:param dict event: request.get_json() result
:returns: A dict of user profile
"""
try:
event_type = event['data']['item']['type']
except KeyError:
raise ValidationError('Unexpected JSON format.')
if event_type != 'user':
return ValidationError('Event type is not supported.')
try:
id = event['data']['item']['id']
email = event['data']['item']['email']
except KeyError:
return ValidationError('User object missing fields.')
return {'id': id, 'email': email}
def call_json_api(request_function, api_name, *args, **kwargs):
"""An simple wrapper for sending request
:param request_function: function used for sending request
:param str api_name: name for this api call
"""
try:
res = request_function(*args, **kwargs)
except:
raise CallExternalApiError('API call failed to %s.' % api_name)
try:
return res.json()
except:
raise CallExternalApiError('Invalid response from %s.' % api_name)
#app.route('/<clearbitkey>+<appid>:<intercomkey>', methods=['POST'])
def webhook(clearbitkey, appid, intercomkey):
"""
Webhook endpoint for Intercom.io events. Uses this format for Clearbit and
Intercom.io keys:
/<clearbitkey>+<appid>:<intercomkey>
:clearbitkey: Clearbit API key.
:appid: Intercom.io app id.
:intercomkey: Intercom.io API key.
Supports User events, specifically designed for the User Created event.
Adds a note to the user with their employment and company metrics.
"""
event = request.get_json()
try:
return handle_event(event, clearbitkey, appid, intercomkey)
except (ValidationError, CallExternalApiError) as e:
# TODO: include **res_objs in response
return jsonify(error=str(e))
def handle_event(event):
"""Handle the incoming event
"""
user = get_user_from_event(event)
res_objs = dict(event=event)
person = call_json_api(
requests.get,
'Clearbit',
CLEARBIT_USER_ENDPOINT.format(email=user['email']),
auth=(clearbitkey, '')
)
res_objs['person'] = person
if 'error' in person:
raise CallExternalApiError('Error response from Clearbit.')
domain = person['employment']['domain']
company = None
if domain:
try:
company = call_json_api(
requests.get,
'Clearbit',
CLEARBIT_COMPANY_ENDPOINT.format(domain=domain),
auth=(clearbitkey, ''))
)
if 'error' in company:
company = None
except:
company = None
res_objs['company'] = company
try:
note = create_note(person, company)
except:
return jsonify(error='Failed to generate note for user.', **res_objs)
result = call_json_api(
requests.post,
'Intercom',
(INTERCOM_ENDPOINT, json=dict(user=dict(id=id), body=note),
headers=dict(accept='application/json'),
auth=(appid, intercomkey)
)
return jsonify(note=result, **res_objs)
I hope it helps.

Categories

Resources