Office 365 APIs and Python Part 3: Mail and Calendar API
This post is the third part of a series of posts chronicling my (mis)adventures learning Python and Django, and using them to integrate with Office 365. This extends the sample app from Part 2 to add CRUD operations with the Mail and Calendar APIs.
I had so much fun making part 1 and part 2 of this sample, I figured why not take it farther and implement a bit of all three of the Exchange-based APIs? In this post, I take on the Mail API and the Calendar API.
Paving the way
My plan was to extend the existing o365service module to include Mail and Calendar functions. But before I started, I decided to try to streamline the existing code a bit. To that end, I added a make_api_call function to the module that handles setting headers and sending the HTTP request. I updated all of the API-specific function like create_contact to use this function.
Being a good citizen
Since all of my API calls are now going through the make_api_call method, I figured this was a good time to add client instrumentation. Now each request's id is written to the debug log. If this was a production application and I needed help from Microsoft to figure out why calls were failing, this forensic data could help expedite a resolution, so it's definitely a recommended practice.
Enable Fiddler capture
Another small change I made while consolidating the HTTP code was to add a global flag to turn off SSL certificate validation. While developing part 1 and 2, I was never able to get Fiddler captures of the calls made by my app. With Fiddler running, I would always get SSL certificate validation errors. It turns out that the requests module will fail requests when the SSL certificate isn't valid. Of course that's a good thing, but it makes it difficult to use Fiddler. No problem though! The requests module has the ability to suppress SSL certificate validation on a per-request basis. If you want to use Fiddler, set the verifySSL variable at the top of o365service.py to False.
Adding the APIs
Adding the Mail and Calendar APIs was fairly easy since I had the Contacts API functions as a starting point. Copying those methods and making a few tweaks was really all it took. The function create_message isn't very different than create_contact. You gotta love REST. I won't go into detail here, the relevant changes are all in o365service.py.
Invoking the new APIs
If you look at the 1.2 update on GitHub, you may notice that there are no new views in the app. That's right! I didn't change the UI of the app to use these new APIs. Instead, I wrote tests. Django has a neat testing framework. I created the MailApiTests and CalendarApiTests classes, and threw in a ContactsApiTests class for good measure. What I like about the way Django testing works is that I can easily invoke all tests with one command, or I can be selective. For example, I can run all of the Mail API tests with this command:
python manage.py test contacts.test.MailApiTests
Or, I can just invoke the test_delete_message test with this command:
python manage.py test contacts.test.MailApiTests.test_delete_message
You have to manually copy a valid access token into test.py to get these tests to run. If you use the app up to the point where https://hostname/contacts will display a list of contacts, you can browse to https://hostname/admin, login as your superuser, and copy the access token from your Office365Connection record. Keep in access tokens expire after an hour.
Expanding coverage
Some of you may have noticed that the o365service module doesn't cover everything. For example, you can work with contacts, but not with contact folders. Messages are there, but not mail folders. Or more subtly, you can get messages from the Inbox, but not from other folders. So what if you want to do more? The make_api_call method can help here. You can add another method to the o365service module, or you can call make_api_call directly! As an example of this, I use it in the test_send_draft_message test to get the messages from the Drafts folder.
Moving on
This will likely be my last post in the Python series. I've had a blast playing with Python and Django, and they seem like a natural platform for consuming the Office 365 APIs. As fun as it's been, it's time for me to move on to another language. What will it be? Stay tuned to find out!*
*Probably Ruby. However, I'd love to hear suggestions from the community. Is there a language or platform you're dying to see the Office 365 APIs run on? Drop a note in the comments.
Get the code
The updated code is posted on GitHub. As always, I'd love to hear your feedback in the comments or on Twitter (@JasonJohMSFT).
Comments
Anonymous
September 13, 2015
Hi Jason, Hope you are doing great. I have a strange issue with office 365 API with basic authorization. I posted the question on stack overflow. stackoverflow.com/.../office-365-rest-api-with-basic-authorization If you get a chance, could you please share you thoughts or suggestionsAnonymous
July 07, 2016
Hi Jason - I'm having trouble with inserting start and end duration window (startDateTime, endDateTime as part of query_params) in your Python Contacts example. Although it's a contacts example, I'm using it for calendar primarily. I'm applying the duration window for reading calendar events. Your help here would be greatly appreciated:http://stackoverflow.com/questions/38258407/how-do-i-insert-start-and-end-date-and-times-for-outlook-calendar-api-callBhushanAnonymous
July 19, 2016
Hi Jason, Hope all is well. Thank you for your recent feedback on some of the issues I was struggling with Calendar API using Python/Django. I meant to ask you if MSFT provides a packaged python library for O365 APIs? I know you've written node-outlook for Node.js, which I've tried and works wonderfully well. Wishing there was something similar available for Python that has outlookservice functions in a package form. I can create my own package in a worst case situation, but would love to use one if already available. Thanks!Bhushan- Anonymous
August 02, 2016
No, we don't have a Python library at this time. Sorry!
- Anonymous