Python FastAPI Tutorial (Part 7): Sync vs Async - Converting Your App to Asynchronous
32:10

Python FastAPI Tutorial (Part 7): Sync vs Async - Converting Your App to Asynchronous

Corey Schafer 17.01.2026 8 705 просмотров 467 лайков

Machine-readable: Markdown · JSON API · Site index

Поделиться Telegram VK Бот
Транскрипт Скачать .md
Анализ с AI
Описание видео
In this video, we'll be learning about synchronous versus asynchronous in FastAPI. We'll cover when you should use async routes, when you should stick with synchronous routes, and then we'll convert our entire application from sync to async. This includes updating our database configuration to use async SQLAlchemy with aiosqlite, converting all of our routes to use async/await, handling eager loading for relationships, and updating our exception handlers. By the end of this tutorial, you'll understand when async actually provides benefits and how to implement it correctly in your own FastAPI projects. Let's get started... The code from this video can be found here: https://github.com/CoreyMSchafer/FastAPI-07-Async-Await Full FastAPI Course: https://www.youtube.com/playlist?list=PL-osiE80TeTsak-c-QsVeg0YYG_0TeyXI AsyncIO Tutorial - https://youtu.be/oAkLSJNr5zY ✅ Support My Channel Through Patreon: https://www.patreon.com/coreyms ✅ Become a Channel Member: https://www.youtube.com/channel/UCCezIgC97PvUuR4_gbFUs5g/join ✅ One-Time Contribution Through PayPal: https://goo.gl/649HFY ✅ Cryptocurrency Donations: Bitcoin Wallet - 3MPH8oY2EAgbLVy7RBMinwcBntggi7qeG3 Ethereum Wallet - 0x151649418616068fB46C3598083817101d3bCD33 Litecoin Wallet - MPvEBY5fxGkmPQgocfJbxP6EmTo5UUXMot ✅ Corey's Public Amazon Wishlist http://a.co/inIyro1 ✅ Equipment I Use and Books I Recommend: https://www.amazon.com/shop/coreyschafer ▶️ You Can Find Me On: My Website - http://coreyms.com/ My Second Channel - https://www.youtube.com/c/coreymschafer Facebook - https://www.facebook.com/CoreyMSchafer Twitter - https://twitter.com/CoreyMSchafer Instagram - https://www.instagram.com/coreymschafer/ #Python #FastAPI

Оглавление (7 сегментов)

Segment 1 (00:00 - 05:00)

Hey there. How's it going everybody? In this video, we're going to be learning about synchronous versus asynchronous and fast API. We'll cover when you should use async routes, when you should stick with synchronous routes, and we'll actually convert our entire application from synchronous to asynchronous. In the last couple of tutorials, we built a lot of functionality. We added full CRUD operations for posts and users, cascade deletions, and put and patch endpoints with proper validation. So, we've built a pretty solid foundation here. Now, before we keep adding more features, I want to take a step back and optimize what we already have. So, in this tutorial, we're going to make our app more efficient by converting to async. In the next tutorial, we'll reorganize our code with routers since our main. py file here is getting pretty large. And then after that, we'll add forms and authentication on top of a solid foundation here. So this polish then build approach is how a lot of real projects tend to grow. Okay. So let's start with the basics. So what is async or asynchronous? So async allows your program to handle multiple tasks concurrently. In my async IO video, I compared this to a McDonald's versus a Subway. With synchronous code execution, which is what we normally write, one thing happens after another. So, it's like going to a Subway restaurant where they make your entire sandwich from start to finish before moving on to the next customer. But in concurrent code, in asynchronous code, it's more like a McDonald's where someone takes your order and then moves on to the next customer while your food is being made in the background. Now, if you'd like a super in-depth explanation on this along with animations to visualize what's going on, then I do have a full video on Async. io, and I'll leave a link to that video in the description section below. But basically, what we're trying to avoid by switching to asynchronous is waiting for something external like a database response, a network request or a file to read because we can do other work during that time and those are called IObound tasks. So those are the situations where we would look to async to improve performance. So things like database queries where you're waiting for a database to respond, external API calls where you're waiting for a network response and file operations where you're waiting for the disk. Now all those things involve waiting and not computing. Uh async does not help with computing or CPUbound operations. So those would be things like heavy calculations, image processing, data crunching. These keep the CPU busy doing actual work. So there's no waiting actually involved there. So there's nothing for async to optimize. So async isn't always faster. That's a common misconception. Uh for simple fast queries, you might not see much benefit. The overhead of the async machinery can even slow things down slightly. Now the real benefits show up when you have a concurrent load, meaning lots of requests happening at the same time. So now let's talk about how fast API handles sync versus async because this is actually pretty interesting. So let me go back to the main py file here and go down to some of our routes. Now when you define a route with just defaf so it's just a regular function fast API automatically runs it in a separate thread pool. So this prevents the function from blocking the main event loop. So even with a regular defaf function here other requests can still be processed and this is automatic and it works really well. Now if you define a route with async defaf uh then fast API runs it directly in the main event loop. So this is more efficient but it means that you must await for any IO operations. If you do blocking IO without a wait, then you'll block the entire event loop, and that's worse than just using a regular defaf function. So, both approaches work. Using regular defaf uh routes isn't wrong or slow. Fast API handles them intelligently. You should choose based on what your specific route does. And if you do use async, just be sure you're using it correctly. So, that's why I started out this series using synchronous routes. But fast API does synchronous well and we have a lot of places in our code where it's going to be useful. Pretty much every route in our current application uses a database. So let's get started and see how to convert this to using async. So first we need to install some dependencies here. So first of all we are using SQL light for now. So I'm going to install a library here called this is called AIOS SQL light. Now, if you're doing a pip install, then

Segment 2 (05:00 - 10:00)

this is how you would do it. Sorry, this is SQL light, but I'm going to be using uv. So, I will do a uv add a iosql light here. And we can see that installed successfully. So, what aiosql lite does is provide an async driver for SQL light. SQL alchemy can then use this driver for async operations. And the same concept applies to other databases. So for Postgress we would use something called psychoppg. But now let's go ahead and start making some changes to our code. So first let's convert our database. p py file here to be async. So instead of pasting the whole thing, let's walk through each change to see exactly what's going to be different here. So first let's update our imports. Now this is the biggest change. So I will grab this from my snippets here and paste these in. So now instead of importing from SQL Alchemy, we're importing from SQL Alchemy. ext. AnYC. io and we get async session maker and create async engine. Now we still need declarative base from SQL Alchemy OM here. So now let's update our database URL. We need to tell SQL Alchemy to use the AIOS SQL light driver. And the way we do that is right here where we are saying SQLite. We just put a plus here and then say AIOS SQL light. So that part there tells SQL alchemy which async driver to use for our SQL light database. So next let's update the engine. So instead of create engine here, we are going to use this create async engine. And now we need to update our session factory here. And this one requires some more changes. So instead of session local, let's call this async session local. Instead of session maker here, we're going to use this new async session maker here. And now I'm going to get rid of everything in here and we will pass in some new arguments. So the new arguments we are going to pass in the engine we are going to set a class here equal to async session and lastly I'm going to do expire on uh commit and we will set this equal to false. Now, I think a lot of these are pretty self-explanatory here, but this expire on commit equal to false, this is recommended for async because it prevents issues with expired objects after a commit. So, when an object expires, SQL Alchemy would normally try to reload it lazily, but lazy loading doesn't work in async, and we'll talk about that more in just a bit. Okay. And finally, let's update our get DB function here to be async. So, this is going to be an async function here. So, I'm going to put async before this here. And now we are going to do an async with and we are going to use this async session local instead. And instead of db here, let me just clarify and I'll call this a session. So, this is still a generator that yields a session, but now it's an async generator. So, let's save that. And now, let's go over to our main. py here, and let me go up to the top for our imports. And now, I'll add a few imports in here. Uh, whenever I save this, this will auto format these imports automatically. So, I'll just add them right here. But first, I'll do a from context lib import. And we will import async context manager and that will be for our lifespan function that we see here in just a bit. Um but now I'm also going to import fast APIs default exception handlers here. So we can use async handlers. So I'm going to say from fast API exceptions actually this is going to be exception handlers here. I'm going to import both HTTP exception handler and also request validation exception handler. Now this JSON response here, we were using this in our exception handlers. I'm going to remove this because we are going to use these default exception handlers from now on. Uh and we will see that changed here in just a bit. But I'm going to remove that for now. And now we need to change our session import. Instead of getting session from SQL alchemy OM we need async session. So I will add that in. Whoops. And actually that is not going to be from the same location here. This is going to be uh from extasync io. And now we want to import async

Segment 3 (10:00 - 15:00)

session. So I'll import that. And lastly here we need to import select and load for eager loading relationships which is super important for async. So this is going to be within SQL alchemy. Oorm and we are going to import select inload. So just to summarize our import changes here. All of these are going to be underlined because we're not using them yet. We imported this async context manager. We added these exception handlers. We removed our JSON response and we imported async session instead of session and added this select and load. And we'll talk about why select and load is important here in just a second. Okay, so now we need to talk about handling creating our database tables. Right now we have this line right here. This base metadata. createall. The problem is that create all is synchronous and we can't just call synchronous methods with our async engine. We need to remove this line and instead create our tables in a lifespan function. So I'm going to delete this. Now lifespan is a modern way in fast API to handle startup and shutdown events. It replaces the older deprecated on startup and on shutdown decorators that you might see in some older tutorials. So I'm going to add our lifespan function right above where we create our fast API app. So first of all, this is going to be an async context manager here. And I will call this lifespan. And within here, we're going to pass in an instance of fast API. And now let me type this out really fast and then I'll explain exactly what it's doing. Okay, so now I've got this typed out. So what is happening here? So the async context manager decorator turns this into an async context manager. Now the code before the yield here. So this here, this runs at startup and I've commented that here. And we use engine. be begin to get an async connection and then run sync here lets us run the synchronous create call method inside of our async context. Then the yield which is where our uh application actually runs and then the code after the yield runs at shutdown where we dispose of the engine properly. Now to get this lifespan working with our app, we also have to say lifespan is equal to and we called this lifespan. So basically what we're doing here all this is an asynchronous way of creating our database tables if they do not exist. If they do exist then again this is item potent which means we can run it multiple times and it won't have any side effects. Okay. So before we convert our routes, I need to explain something critical about async SQL alchemy. And this is probably the biggest difference between synchronous and asynchronous and it trips up a lot of people. So in synchronous SQL Alchemy, lazy loading just works. So when you have a post object and you access post. author, author SQL Alchemy automatically runs a query to load that author because that is a relationship and your templates can access post. author. username without any issues. That's called lazy loading. Now in async SQL alchemy lazy loading is not supported. If you try to access post. author without having explicitly loaded it then you get an error. uh the error is something like missing green lit or something like that. This happens because lazy loading would require running a synchronous query in an async context and that's not allowed. So the solution is eager loading with select and load that we imported earlier. So instead of letting SQL Alchemy lazy load relationships when you access them, you explicitly tell SQL Alchemy to load them immediately with the main query. And we'll see how to do that in just a second. But basically the point that I'm trying to make here is that any query where you'll access relationships like template routes that show post. author or API routes that return post response which include the author. we need to add eager loading. Okay. So now let's convert some of our routes here to be asynchronous. So first let's just do our home route. So first instead of defaf here I'm going to say async defaf and now instead of session we are going to use async

Segment 4 (15:00 - 20:00)

session and we want to add await to our database execution here. So right where we have result is equal to db. execute execute. We want to await that DB execution. And now we need to add in select and load to load that author relationship. And this is what I was talking about with eager loading. So right after we do uh select models. post here, I'm going to do a options and then we will do select and load. And what we want to load is models. post. author. And I'll put a comma there just for formatting. So then when we iterate over post in our template and access post. author, that is going to work because we already loaded in that data. Okay. So now let me update another route here so we can kind of see what this looks like and get the hang of it. So let's do the post page here. And just like before, I'm going to do async defaf instead of just a regular function. We will do an async session here instead of a regular session. We want to await our database execution here. And just like before, let me get this options here. And then we will put this before our wear clause here. So let's add that eager loading in there. And that's all we need to do for that route. Okay. So at this point, I think you can see the pattern that we're going for here. We're updating our function definitions to use async. We're changing our database session to async session. We are awaiting database executions and we're adding in select and load where we have relationships that normally would have been lazy loaded. So now since we're using a database in every route that we currently have, we need to change all of these. Unfortunately, I know that we changed all of our routes in the database video as well. So this could get a little repetitive here. So instead, I'm just going to paste in the rest of the routes one by one. And I'll call out key things as we go here. So within these snippets here, our next route is this user post page here. So let me grab this and I will paste this in and replace our user post page here. So let me save this and we'll take a look. So again we're using async uh we are using our async session we are awaiting our database executions. Now here's one thing that I want to point out. Now notice that the first query here to check if the user exists. It doesn't need that select and load because we're not accessing any relationships on the user object. But the second query for post does need it because our template accesses post. author. So it kind of requires us to know what relationships our databases uh have and the queries that we're making. Okay. So I'm going to keep going down here and replacing our other routes. So right now we're at create user. So create user. I will grab this route and copy that and replace this. Now this is pretty much the same. We added async. We added our async session. We're awaiting our database executions. And one thing I do want to point out down here at the bottom, notice that we are awaiting dbcommit and db. refresh. But notice that db. add does not get an await. That's because add just adds the object to the sessions pending list in memory. It doesn't actually do any IO. The actual database operations happen at commit and at refresh here. Those need a wait, but add does not. Okay, moving on. Here we have our get user route. So, let me grab that and replace that with our async route. There's nothing really to discuss there. That's basically the same for get user post. Let me grab that and let's replace that. Now, for this one, there's not much to talk about here. Uh, since we uh have this post response as our post model, we do need this select and load here to do the eager loading on that author. Okay, moving on to update user here. Let me grab all of this and I'll paste this in. And again, just like the other tutorials, all of this code will be available in the description section below. if you do not want to type all this out yourself. So basically the same concept here. We changed this to async. We're using async session. We are awaiting the database

Segment 5 (20:00 - 25:00)

executions uh and awaiting the dbcommit there. Okay. And moving on to delete users. Let me grab that and paste this in. Now there is one thing I want to point out on this one. And notice that the db. delete delete. It does need a weight. Uh that's different from db. add. The delete operation needs to interact with this uh session in a way that requires a weight and async mode. Okay. And now I am going to go ahead and do the same for all of our API post routes. So going down through here, we have get post. And I think I am done pointing things out here. So I'm just going to go through and we will replace all of these with their async versions here and then we will discuss at the end once we are done. So get post we just have a couple of more here. Okay. So we replaced all of those routes. But there was something that I wanted to discuss up here. If I look at our post routes here, we can see on this db. refresh here, we have this attribute names equal to author. This is in our create post route. So what that does in our refresh call, that's pretty important. So when we create a new post and then return it, we need the author to be loaded for the post response. So instead of doing a separate query with select and load, we can tell refresh to also load specific relationships using the attribute name parameter. So await db. refresh uh using this here will refresh the post and load the author relationship. And the other place that we did this was in update post full. We did that as well. And I believe those are Oh, and we also did that in update post partial. That's right. Okay. So, that was a lot of changes, but the pattern is pretty consistent. We make our functions async. We await our uh database executes, and we await our commits and our refreshes to the database. And we use select and load or attribute names whenever you need relationships loaded. Now another thing that we're going to update here are our exception handlers. So our current handlers are synchronous and we are creating a JSON response here manually. Now a better approach is to use fast API's default handlers which are async and this gives us consistent behavior with the rest of fast API and it is less code to maintain. So I'm going to convert this to an async function here. And now for our API route check here. So if the URL. path starts with for/api instead of returning a JSON response, we will await fast API's default handler. So all I'm going to do here is I'm going to replace this and I'm going to do a return await. And we imported this earlier. This is HTTP exception handler and we are going to pass in the request and this exception here. So we will pass in that exception as well. So you can see we're not no longer using that message. So what we can do is we can just take this message. We're only going to be using this for the template now. So I will have that down here. And now that message will only be computed for template routes since we don't need it for our API routes anymore. So [snorts] let's do the same changes here to our validation exception handler. So same thing here we are going to make this an asynchronous function and we are going to replace our JSON response here. We are just going to do a return await and that will be request validation exception handler. We imported that earlier as well. And then again we can pass in the request and that exception. Okay. And that should be all of the conversions that we need to make that uh makes our application asynchronous now. So now let's test this out and make sure that everything still works. So, in my terminal here, let me run our fast API server. Okay, it looks like we got an error here. Let me see uh what this could be. So, it looks like it's up here with our lifespan. Let me

Segment 6 (25:00 - 30:00)

see if everything is typed correctly up here. And if not, then I will pause just for a second to uh figure out exactly what's going on here. Okay, so I paused the video for a second to look this up. Actually, the error is pretty clear. No module named green lit here. Uh I forgot in my script to also add green lit to our installations here. So let's go ahead and do that where we installed a SQL light. Let's also install green lit. Uh we need this for SQL Alchemy's async mode, but it doesn't always come automatically installed. So now hopefully that will fix our problems here. Let me rerun the server. Okay, so the startup is complete. Let's go test our application. The main page is working. If I reload our post here, then we have our post. Let me go to the documentation and reload this. Okay, so all of this seems to be working well. uh we can navigate through and just for the heck of it, let's go ahead and create a post as well. So I'll go to try it out. Now I already have a user with a user ID of one. Uh testing async content. Let's execute that. And that worked well. Let's reload our page here. Okay, everything seems to be working fine. Now if we actually look at the response here uh if I reload all of the posts from our API then we can see here that all of that author information with the post is coming in. So that means that our eager loading is working. the select and loads and also the attribute names and the refresh those are working correctly. So externally everything works exactly the same as before. All of the difference here that we've made to our application is internal. Our application is now using async operations which means that it can handle more concurrent requests efficiently. So let's talk about when you should actually use async. So you want to use async when you're making multiple independent IO operations. You want to do it when you are calling external APIs. That's probably the best use case. uh if you have database operations under high concurrency and if you have longunning IO operations now you want to use regular synchronous functions when you have simple fast operations when you are doing CPU work like calculations or image processing if your code clarity matters more than marginal performance gains and if you are only using sync only libraries now here is something important uh so You do not have to choose just one or the other. A mixed approach is totally fine and actually pretty common. So some routes you can have be async and some can be synchronous. Fast API handles both gracefully. So you should choose based on each specific routes needs. Now all of my routes here, they talk to the database. So we had to change all of our routes. But you could have synchronous routes if you just had some standalone routes. Now let me talk about some common things to avoid. So first you do not do blocking IO in async functions. So don't use synchronous database sessions in async routes and don't use something like the request library in an asynchronous route. The request library is a synchronous library. You would want to use something like httpx instead. Now, in terms of performance gains, the benefits of async really show up under load. So, for a single request at a time, like we're doing here, we're not going to see really any difference. Where async shines is when you have many concurrent requests. So, for learning and development, either approach is fine. Don't optimize prematurely for production under load. Async can handle more concurrent connections. But if asynchronous code is a little bit intimidating to you, then we have been using synchronous routes throughout all of this tutorial uh before we got to this point. So if you want to use synchronous, that's completely fine. You can go ahead and push out whatever uh you are working on and then look at your logs and look at the performance that you are getting and if you need to optimize with async, then that's always an option. Okay, so let's do a quick recap of what we changed with our application here. So in our database, we switched to the create async engine here with an AO uh AIOS SQL light driver. We replaced our session maker with an async

Segment 7 (30:00 - 32:00)

session maker. And we made get db an async generator here. And in our main py file, we added a lifespan function for table creation at startup and engine disposal at shutdown. All of our routes are now asynchronous since they all use the database. For the database operations, we are using a wait. Uh our exception handlers at the bottom use fast API's default async handlers. And for eager loading relationships we are using this select and load here. And also if I find one then also when we refresh we are using this attribute name here within refresh also to uh eager load relationships after commits. Okay. So in the next video we're going to organize our code with routers. So our main. py py file, which we've probably been able to tell here, is getting pretty large now. So, it's going to keep growing as we add more features. So, what we're going to do is we'll break it into organized modules using API router for post and users. Now, it's similar to Flask blueprints if you're familiar with those. But I think that's going to do it for this video. Hopefully now you have a good idea how to use async and await in fast API and when it makes sense to do so. But if anyone has any questions about what we covered in this video, then feel free to ask in the comment section below and I'll do my best to answer those. And if you enjoy these tutorials and would like to support them, then there are several ways you can do that. The easiest way is to simply like the video and give it a thumbs up. Also, it's a huge help to share these videos with anyone who you think would find them useful. And if you have the means, you can contribute through Patreon or YouTube. And there are links to those pages in the description section below. Be sure to subscribe for future videos and thank you all for watching.

Другие видео автора — Corey Schafer

Ctrl+V

Экстракт Знаний в Telegram

Экстракты и дистилляты из лучших YouTube-каналов — сразу после публикации.

Подписаться

Дайджест Экстрактов

Лучшие методички за неделю — каждый понедельник