Using the Twilio Python Helper Library in your Async Applications
Time to read: 4 minutes
If you are using an asynchronous web server with your Python application, you are bound by the first rule of async development, which is to never call functions that block. So what do you do when you need to use packages such as the Twilio Python Helper Library, which has no asynchronous version?
In this article I’m going to show you some of the options that you have to safely integrate Twilio and similar clients with your async application.
What is the problem?
Before I go into the solutions, I thought it would be good to give a quick review of the issues with blocking in asynchronous applications.
At the core of every async application there is the loop, an efficient task manager and scheduler that ensures that the CPU is shared as fairly as possible among all the running tasks. The type of multitasking used by async applications is cooperative, which means that tasks have to get a little bit of work done and then voluntarily suspend and return control to the loop.
The most common place where tasks can suspend is when they need to wait for I/O. Good examples of this are querying a database or sending an HTTP request to a third-party service. Right after these operations are issued, the task has nothing to do other than wait for a response from the other side, so at this point the task tells the loop what it is waiting for and returns control to it, so that it can find another task that can use the CPU in the meantime.
If you get a task that runs for too long without suspending and returning control to the loop, then all the other tasks starve. In a web application, this would mean that the web server would be completely blocked and unable to accept new requests, or even make progress on current ones, until the rogue task releases the CPU to the loop.
Often developers of async applications do not realize that they are calling blocking functions, and inadvertently introduce periods of task starvation, which greatly affect the performance and responsiveness of the application. This would happen if you use the Twilio client for Python, because this client uses the Python requests library to send HTTP requests, and this library is not compatible with asynchronous applications.
Below you can learn about four different ways to use Twilio services from your async application in Python without blocking.
Solution #1: Sending raw HTTP requests
A solution to this problem is to not use the blocking Twilio client, and instead send raw HTTP requests using an async HTTP client library.
From the Twilio documentation, we learn that to send an SMS we have to send the following request:
We can take this curl
example and translate it to Python, using one of the asynchronous HTTP clients. Using aiohttp, we can write an async_sms.py module as follows:
And now we are ready to integrate this function with an asyncio application:
If you plan on testing the above example, remember that you have to set the TWILIO_ACCOUNT_SID
and TWILIO_AUTH_TOKEN
variables in the environment. You can find the values that apply to your Twilio account in the Twilio Console.
This solution can be extended to support any functionality supported by the Twilio client, but of course this is somewhat laborious, as you will need to browse through the documentation to find the correct HTTP requests that map to the functions that you want to implement.
Solution #2: Monkey-patching
This solution only applies if your application is based on the Gevent or Eventlet async frameworks. If your application is based on asyncio or a derived framework, then this one is not for you so skip ahead.
Monkey-patching is a feature of the Gevent and Eventlet frameworks that replaces the blocking functions in the Python standard library with equivalent asynchronous versions. The low-level socket and networking functions are patched so that whenever a call is made that requires waiting for a response the task is suspended and other tasks get the chance to run.
Consider the following standard Python module sms.py that sends an SMS with the Twilio client:
In a traditional Python application you would use this function as follows:
If you want to make this application work under Gevent, you could do this:
The following version is similar, but it applies to Eventlet:
As you can see, the only difference is that at the very beginning we call the appropriate monkey-patching function that installs a “fix” in the networking code that enables it to work with the async loop.
Solution #3: Using a thread executor
The asyncio package provides a cool trick to be able to run blocking code safely. The idea is to execute the blocking function in a separate thread, so that it does not affect the loop.
For this solution you can use the original send_sms()
function from sms.py I have shown above, but instead of executing it directly we use the run_in_executor() function to send it to run in a thread pool:
The first argument to run_in_executor()
is a concurrent.futures.ThreadPoolExecutor instance that can be configured to your liking. If None
is provided, then an executor with default options is created the first time the function is called and used from then on.
Solution #4: Using greenletio
The final solution is somewhat similar to the monkey-patching option available to Gevent and Eventlet users, but for the asyncio package. This is based on the greenletio package, which allows some blocking functions to be used in an asynchronous environment through monkey-patching of low-level blocking code in the Python standard library.
The implementation uses the patch_blocking()
function to wrap the import of our SMS sending function. This makes sure that any I/O accesses issued by this function are patched with asyncio friendly equivalent functions. To complete the conversion, the async_()
wrapper is applied to send_sms()
so that it becomes awaitable.
Note that the greenletio package is fairly new. It is perfectly fine to use in your personal projects, but not something I would recommend as a production-ready solution as of yet.
Conclusion
So there you have it, four different ways to use a blocking Python library in an async application!
Once again I’d like to emphasize that these four solutions apply not only to the Twilio client, but also to any other packages that issue network requests to other services such as databases or APIs.
I hope you enjoyed this article and learned a few new ways to unblock your async applications!
Miguel Grinberg is a Python Developer for Technical Content at Twilio. Reach out to him at mgrinberg [at] twilio [dot] com if you have a cool Python project you’d like to share on this blog!
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.