 You want to present today a talk that shows us how to go from synchronous flask to asynchronous sonic. Is that correct? Pretty much. Yeah. Pretty much it. I'm really looking forward to this. So if you have your screen shot screen share ready, then please activate it and I'll give you the spotlight and hope you have a great talk. Hi everybody. My name is David and today we're going to talk about flask and kawait. Before we get started, I'll tell you a few words about myself. I'm a husband, I'm a father of two boys, I'm a Python-y stuff for the last decade or so and I'm a scale nerd. In addition, I recently started working for NVIDIA as a software architect. Before we get started, I want a set of expectations. This session is more of a mind-opener on how added value can be provided with minimal effort. This session is definitely not about saying that this technology is good or that technology is bad. That's not the message here. In addition, I'm assuming that all the attendees have prior knowledge in web development and in REST APIs in particular. So first, let's get started with some motivation. You can see the code on the left. The code on the left is pretty basic flask code and the code on the right is pretty basic Seneco. So if you take the code on the left and you transform it to the code on the right, as you can see, there aren't that many differences, then you can get between three and four X performance improvement, which is pretty nice. Here you can see the results of my benchmark. The benchmark that I did is 10,000 requests with 100 concurrently level. As you can see, for flask, this took around 750 seconds. I'm sorry, for flask, it took about 750 requests per second with Senec, around 2,500, and the time per request correlated as well. For flask, it's 130 milliseconds, and with Senec, it's 40 milliseconds. Some extra notes about my motivation experiments. For flask, I installed simple JSON as an option of dependency, as recommended in their documentation. By the way, whenever you see this sort of link in my presentation, it is a link. So if you'll get to the presentation later, everything is linkable. And the tool I use for benchmarking is a tool called AB, which stands for Apache benchmarking. So a couple of words about flask. Flask is a microwave framework that revolutionized how web is developed with Python. By microwave framework, meaning that as opposed to Django, the Django is what's called a framework, a web framework with batteries included, flask does not include almost anything by its own. Whatever you need, anything, I don't know, cookies, authentication, whatever, you need to either implement yourself or use a third party. To my opinion, it revolutionized web in Python because of its simplicity, because of its major simplicity. Async.io. Async.io is a library to write concurrent Iobound code using the Async await syntax. What's Iobound code? Iobound code, for example, is making HTTP requests, an example for something that's not Iobound or to be more accurate to something that's CPU bound is compression. This line is important for the next couple of slides. So why Async.io? What's wrong with, I don't know, thread per request or process per request currently? We consume more HTTP services than ever. We can easily reach 10,000 concurrent connections on a single server, aka the C10K problem. As a result, a cooperative task that can better utilize the CPU on a single machine can save us a lot of money. Sineq is Python 3.6 and above a web server and web framework that's written to go fast using the Async await syntax. Now I would like to introduce to you to Pyaday. Pyaday is an application. Well, it does several things. It's a crud for Python packages metadata that's used by its content curators. And you can get your daily random Python package in for front-end profit. So here's the gist of it. When you call a route named run, then you get a response JSON with info on the random Python package and now you can learn, like, new stuff pretty fast. I used HTTP Py for this demo, but you can use curl or whatever as well. This is how the main of the app looks like. You call the Flask constructor and you register two blueprints, blueprints for packages and blueprints for run. The way I look at blueprints when it comes to REST, I like to put in one blueprint all the HTTP methods of a certain REST resource that I would like to consume. So if we're talking about my example, where we talked about packages crud, then all the crud operations of the packages route will all be in the same blueprint. And the run route will be the same route on the round blueprint. Here you can see a couple of imports and here you can see our glorious database, which is a list of dictionaries. You can also see we're in at the blueprint here. Now let's go through the crud methods, start with the C, the create. Whenever there is a post to packages, then I add a new package to our database. And as accustomed in REST, I return a 201 status code, an empty response, and I return, like, a reference to the newly created package on the location header. Moving on to the R, the read. Here you can see a GET request where we get a package name as input. We check for the package with that name in our database. And if it exists, we return a JSON response with it. Otherwise, we return 404 response with an explanation and a relevant content type. Moving on to the U, the update. Here you can see there's a put method. It can also be a patch method. It depends on what do you do. Again, we search for the package in our database. If we find it, we change, we update it and return a 204 status code with an empty response. Otherwise, as previously, we return a 404. And the delete method, again, gets the package name as input. If it finds it in the database, it deletes it and returns a 204 status code. Otherwise, it returns a 404. And now you can see the run blueprint. Again, we ended the blueprint and there is a GET route that returns a random package from our database. So why convert? If you have a large-scale, expensive cloud deployment or you have limited resources on premise deployment, you could get a better bang for your buck, meaning it could save you a lot of money. In addition, we show you that demigration is not difficult and all the flash knowledge accumulated this far is not wasted. So let the conversion begin. As a prerequisite, we need a project that could benefit from the conversion, meaning that it mostly does IO. And it's written in Python version between 3.6 and 3.8. I used Python 3.3.3. Next step is not mandatory. It's my preferences, my preference, poetry in it and poetry at SNEAK. Poetry is a dependency manager. The great thing about it is that you'll get a log file out of the box, so you never need to remember to add something to your requirements file. Again, there is a reference here. You could follow the link later. In addition, for the conversion, I used Flask version 1.1.2 and SNEAK version 20.3.0. So if you're using other version, then it probably means that syntax may vary a bit. So let's get started. Let's get started with the constructor. Pretty straightforward. Instead of importing Flask from Flask, we import SNEAK from SNEAK and we change the constructor call. And that's it. Let's talk about routes. Before we, you can see, again, the curator remains the same at app.route. But here you can see a difference, couple of differences. From Flask, the request object is globally imported. And with SNEAK, it is the first argument. It's a dependency injection. So whenever you have a route, the request is always the first argument. Another difference you can see here that on SNEAK, the route is a coroutine. A coroutine is a function that uses the icing keyword, as you can see here. Now let's go through the JSON response. There are a couple of options with Flask for the happy scenario. You can either return a dictionary or you can import JSONify and return the dictionary or a key word, args inside the JSONify. And it basically wraps the response with the response object and sets the content type, the mind type, and the status code. At SNEAK, there is one main way to go from SNEAK response to import JSON and you wrap your response dictionary with the JSON. And now for the not so happy part or the end link, there is this paper called RC7807 that talks about how errors should be handled in web APIs. So my example is try to follow this RFC. With Flask, you import the response object and then you instantiate the response class. You give it a status four or four content type of application problem class JSON instead of just application JSON. And you give it a response body. We have to dump our dictionary. So this is what we do here. With SNEAK, it's pretty much the same. We return the same response.json object. We give it a body, change the content type and give it a custom status. There are other options for Flask as well. You could also use the JSONify that we saw earlier and change the status because it's default is 200 and return it. Also reload for a great feature for development purposes. For Flask and SNEAK, happily it's the same. There are other options as well. For instance, for Flask, you can, from your terminal, init a virtual and called Flask underscore and if you set it to a development, then you get out to reload. And with SNEAK, you could start the app with a parameter called auto reload equals true. And then you don't get, like, debug verb or output or whatever. You only get the auto reload that you need for development. Blueprints. Again, blueprint is used for sub routing. It contains all the exposed methods of certain routes. The main difference here except for the import path is that SNEAK does not require an import name as an argument to the blueprint constructor. In addition, when you register the blueprint to your app, with Flask you call register blueprint and with SNEAK you call blueprint. You can use register blueprint as well, but it is marked as deprecated and I only wanted to give you the latest and greatest. So now let's look on how our app looks like post conversion. As you can see here, there's a difference with the import paths and for the blueprint constructor, you don't need the import name. For the create method, you have the icing keyword, you have requested the first argument and instead of initializing a response object and set the response to none, you call from response.empty and that's the main difference here. For the read, instead of returning the JSONify, you return the response to JSON. As I said earlier, request is the first argument, but we're not using it, so I put an underscore instead, which is how you mark an unused variable in Python that you must pass. In addition, you have the icing keyword and we already talked about this response type. For update, again, you have the icing keyword, the request is the first argument and instead of returning a response object which the response is none, you simply return a response.empty and for the form code, instead of returning a response object with JSON.dumpset response, you return a response.json with your dictionary as body and with delete, it's pretty much the same, so I won't repeat myself again. Now let's talk a bit more, let's talk a bit about testing. The main difference here is that with Flask, you get the test client through a method and a context manager, with Sineq, you get it through an attribute. When you call server methods, with Flask, you get the response as object and with Sineq, you get a request and a response as object. Again, we're not using the request here, so I put an underscore instead. Another difference is that with Flask, you check the status code by an attribute called status sample score code and with Sineq, the attribute is called status. Here you can see the testing diff, you can see that apart from the changes we talked about at the previous slide, everything is pretty much the same, including how you pass a body to the poster class, you see here a JSON that gets a dictionary, you can see that everything here is pretty much the same. There is another option, Sineq test can also be a synchronous, for this you need to add an additional package called Python Sineq, and if you do that, there are a couple of differences. You get only response as a result and not a request and a response, but you need to await whenever you call the server, other than that, it is the same. Okay, so let's talk a bit more about deployment. I used here, I tried throughout my presentation, I tried to use recommended stuff by the creators themselves and not use like other stuff, so here as well. I used Geonicorin to deploy my app, for Flask I used the default workers, for Sineq I used the Uvicorn workers, and I repeated my benchmark from the beginning, meaning I used AB and 10,000 requests with concurrency of 100, and for the round route, we got between 5 and 6x performance, as you can see, the request per seconds instead of 1,065 hundred, the time per request instead of 98 milliseconds, it's now around 15 milliseconds, which is pretty awesome. So everything sounded already pretty awesome so far, but it's not always a fairytale, because now you have a cognitive burden, meaning that to get a performance, an effective and an effective synchronous code, the event loop must never be blocked, meaning whenever you do an IO, you should await it, like we saw earlier for the asynchronous test example, and whenever you have something that's CPU bounded, like compression we talked about earlier, then it should run elsewhere, it could run in executor or in another process, but something that CPU bounded should never block the event loop, otherwise the code wouldn't be effective. In addition, Sineq's ecosystem is not reached as Flask's ecosystem, it is noticeable on Github, you can see the awesome Sineq project has around 9,000 stars, and the awesome Sineq has around 250. It is also noticeable on the number of available tutorials and on third-party integration for stuff that is pretty useful for web applications, like Swagger Codgen or Zero or Rope Top. In addition, when you're using a third-party library, you need to use its version that's not blocking IO, meaning that if you're used to using PsychoPG2 as your process driver, you cannot use it anymore because it will block the event loop, and you need to use async.pg or ai.opg. Instead of using Request, you can use HTTPX or aio.http, instead of using Redis, you can use aio.redis or async.io.redis, and you get the picture, you understand where it's going. By the way, that's the reason I didn't use a database for my application because I wanted to make my comparison simple and fair, and I didn't want the database driver to have a part in the decision of what happened, of why there are performance differences. Now I'm going to talk to you about the Async Web Framework landscape. I chose Cineq for the stock for several reasons. It's very popular on GitHub. It has around 4,000 stars. In addition, the API it exposes is very similar to the API Flask exposes. When the API is different, it seems like a reasonable evolution that made possible because there wasn't too much of a need for backward compatibility. It is also backed by a community-run organization. At least for me, it's a flashback for the 90s. In addition, there is Quart that its creator talked yesterday, Philip Jones, is also Flask-like Async Web Framework. In addition, there is Fest API. I call it a Hybrid Web Framework because it supports both synchronous and asynchronous with dependency injection as a guiding principle. You don't have a global context that contains everything, but each route that needs to get what it needs to get and its signature, which is pretty awesome. So let's conclude. When a Flask application that mostly performs IO becomes resource hungry, it is worthwhile to convert it to a Cineq app, and the effort for it is reasonable. But after you convert the app, the code must be IO and CPU-aware in order to not block the event loop. That's it. Thank you very much. Any questions? Yes, we have questions. Let's first see. Maybe you can stop your screen share, unless we need to share more things. We had a lot of votes for people who came in late, and so I have to ask you that question again. Is Cineq production stable in your opinion? Yes, I used it for very focused internal web products that only did IO, and for that it was very stable. People are asking what's the main difference between Cineq and Fast API, if you have any experience with that? I'm a bit less experienced with Fast API, but with Cineq there is an event loop, and everything needs to be asynchronous. With Fast API, you can have a mix-in. You can have several routes that are synchronous and several routes that are asynchronous. In addition, whenever, let's say, if you have a Flask application or Cineq, it's the same, and you have a database, then you hold it in your context, and then you get the context. But for Fast API, the database connection or session or whatever is an argument to the route. That's the main difference there, at least the way I see it. In your benchmark you showed for Cineq, it looks like for Cineq less bytes were transferred. Could this explain the part of the speed increase you noticed? I think I'll recheck my presentation again, but I think it might be like either like an end line or something that ended by something, but it was pretty obvious that for IO it can perform better. Okay. Somebody says, without mentioning his name, because it says an anonymous attendee, maybe it's a stupid question, but I've never used a Cineq. If I'm using a serverless function, for example, Amazon Lambda, is there no advantage in being asynchronous? Is that correct? Do AWS functions have to be synchronous or does this give me any advantage? I think it depends on their implementation, but I think that with AWS Lambda, you're paying for your execution time. But if it reduces your execution time, then you can pay less. But I think it's worth benchmarking before making a decision. Yeah, but there's not really a reason to be asynchronous otherwise. Okay. So maybe this was a stupid question, but actually they're not supposed to be any. So thank you for that question too. And the last one I have here at the moment is a question, if in the programming, do we always have to use the word await if the function is asynchronous at all? Or is there others where you don't have to await? You always have to await if you don't want to block the event loop. That's how I think AIO works. Python has other frameworks that work as well. I wrote an async program in Python 10 years ago, and I didn't have to await anything, but for async AIO-based, you have to await everything. Again, because otherwise you would block your event loop and you'll get performance degradation that you cannot expect. Okay. So this is all the questions we have in the Zoom chat, but there has been a rather large discussion in the talk flossing await Discord channel. So if you have any more questions, please go over to that channel. And David, I think it would be nice if you could join that for a while so that people in their lunch break maybe ask some more questions. I learned a lot. I'm really happy for your talk. So let's thank you about this.