I've been writing a API procedure to test posting to an http RestAPI with JwToken authentication. In this scenario it's for patient management system and I'm generating an appointment. The API business rule does not allow duplicate bookings for the same time.
I'm using a python 3.5.3 Virtual Environment (writing in pycharm IDE) and run my tests using the Pytest frame work. Also using PyJWT 1.5.2. , Requests 2.18.3, simplejson 3.11.1 and urllib3 1.22 is installed (I assume Requests is utilising urllib3). I'm using simplejson.dumps instead of the normal json.dumps because the virtual environment didn't have that library and I had trouble adding it. As far as I can tell simplejson has the same functionality for the dumps procedure.
Using the below code I find I can run requests.post call to deliver a Json data payload successfully and generate a post, but then it seems to subsequently perform a 2nd post which generates a 409 conflict error. I have access to the a log database on the corresponding api server and can see that it has in fact tried to post twice, but I can not work out why this occurs and I think there is something in the requests library that is being called twice. Possibly due to the Json I'm posting.
The output looks like this:
https://targerserver.url.com.au/API/Core/v2.1/appointment/
200
{'statusMessages': [], 'appointment': {'startDateTime': '2017-08-15T11:00:00 +10:00', 'appointmentReferenceNumber': '39960337', 'notes': '', 'clients': [{'clientId': 'abeff2be-ce6e-4324-9b57-e28ab7967b6c'}], 'status': 'Booked', 'locationId': 'd8d4fe7c-765a-46a3-a389-54ce298a27e9', 'notifyPractitioner': False, 'endDateTime': '2017-08-15T11:30:00 +10:00', 'subject': 'Jim Beam ', 'appointmentId': '08b37ce3-25e1-4e2a-9bb7-9ec2d716f83b', 'practitioner': {'practitionerId': 'a630f4ad-8b4b-4e06-8cee-7db56ba8b9bf'}}}
collected 1 item
test_PMSAPI_availability.py https://targerserver.url.com.au/API/Core/v2.1/appointment/
409
My Json requires an object (which is a dictionary) and also a list of keys for another field (that has one entry in it) and I'm wondering if the requests library is not handling this. This is a sample of what the json looks like
payload_str = {"startDateTime":"2017-08-15T11:00+10:00","endDateTime":"2017-08-15T11:30+10:00","practitioner": {"practitionerId":"a630f4ad-8b4b-4e06-8cee-7db56ba8b9bf"}, "locationId":"d8d4fe7c-765a-46a3-a389-54ce298a27e9","clients":[{"clientId":"abeff2be-ce6e-4324-9b57-e28ab7967b6c"}]}
I've similar code that has worked for Get calls on the same system, but posting the Json really seems to be problematic. We have other tools for making calls to the same API endpoints which do not seem to have this trouble. The logs would suggest that the JSON data I deliver is identical to that from another tool with the same data.
What I can see from the out put is that is receive a successful 200 code on the initial response, but then if query response.status_code it has become a 409 response. I also tried not doing anything with the response in case that was causing the request to requery and generate the conflict.
My code looks like this:
import jwt
import _datetime
import requests
import simplejson
from requests.packages.urllib3.exceptions import InsecureRequestWarning
from string import Template
def app_undertest_credentials(keyname):
app_credentials = {'app_consumer_id': 'MyTestApp',
'app_consumer_secret': 'where my secret goes',
'app_access_token': 'where my access token goes',
'base_url': 'https://targerserver.url.com.au'
}
return app_credentials.get(keyname)
def end_points_dict(keynameStr, versionStr):
end_points = {'location': '/API/Core/$version/location/',
'practitioner': '/API/Core/$version/practitioner/',
'availabilityslot': '/API/Core/$version/AvailabilitySlot/',
'client': '/API/Core/$version/client/',
'healthfundproviderlist': '/API/Core/$version/healthfundproviderlist/',
'timezone': '/API/Core/$version/timezone/',
'clientgroup': '/API/Core/$version/clientgroup/',
'appointment': '/API/Core/$version/appointment/'
}
lower_keynameStr = keynameStr.lower()
url_extension_no_version = Template(end_points.get(lower_keynameStr))
url_extension_with_version = url_extension_no_version.safe_substitute(version=versionStr)
return url_extension_with_version
def test_api_appointment_post():
# Set Client app credentials
app_consumer_id = app_undertest_credentials('app_consumer_id')
app_consumer_secret = app_undertest_credentials('app_consumer_secret')
app_access_token = app_undertest_credentials('app_access_token')
base_url = app_undertest_credentials('base_url')
end_point_url_sfx_str = end_points_dict('Appointment', 'v2.1')
httpmethod = 'POST'
# Create dictionary for json post payload
data_payload = {'startDateTime':'2017-08-15T11:00+10:00',
'endDateTime':'2017-08-15T11:30+10:00',
'practitioner': {'practitionerId':'a630f4ad-8b4b-4e06-8cee-7db56ba8b9bf'},
'locationId': 'd8d4fe7c-765a-46a3-a389-54ce298a27e9',
'clients': [{'clientId':'abeff2be-ce6e-4324-9b57-e28ab7967b6c'}]
# Create claims dictionary payload for generation of JwToken
claims = {
'iss': 'http://myappsdomain.com.au',
'aud': 'https://targetservers.domain.com.au',
'nbf': _datetime.datetime.utcnow(),
'exp': _datetime.datetime.utcnow() + _datetime.timedelta(seconds=60),
'consumerId': app_consumer_id,
'accessToken': app_access_token,
'url': base_url + end_point_url_sfx_str,
'httpMethod': http_method
}
#create jwtoken and then convert to string
encoded_jwt_byte = jwt.encode(claim_payload, app_consumer_secret, algorithm='HS256')
jwt_str = encoded_jwt_byte.decode()
#Create authentication header
headers = {'Authorization': 'JwToken' + ' ' + jwt_str, 'content-type': 'application/json'}
uri = base_url + end_point_url_sfx_str
response = requests.post(uri, headers=headers, json=datapayload)
print(response.status)
print(response.json())
response.close()
I'm considering using wireshark to figure out what my call is actually sending but I suspect that will just tell me that the calls are being sent twice