#147: Quart: Flask, but 3x faster Transcript
00:00 There have been a bunch of new Python web frameworks coming out in the past few years.
00:03 Generally, these have been focused solely on Python 3 and have tried to leverage Python's
00:08 new async and await features.
00:09 However, these frameworks have come with their own new APIs.
00:13 They may be amazing, but it's still something new to learn and a barrier to migrating over
00:18 to them and between them.
00:20 That's why when I learned about Cort from Philip Jones, I was excited.
00:24 It's an async-enabled web framework that attempts to be 100% compatible with Flask,
00:29 including the extensions.
00:30 This is Talk Python to Me, episode 147, recorded January 18th, 2018.
00:37 Welcome to Talk Python to Me, a weekly podcast on Python, the language, the libraries, the
00:55 ecosystem, and the personalities.
00:57 This is your host, Michael Kennedy.
00:59 Follow me on Twitter, where I'm @mkennedy.
01:01 Keep up with the show and listen to past episodes at talkpython.fm.
01:05 And follow the show on Twitter via at Talk Python.
01:08 This episode is brought to you by SmartKets and Rollbar.
01:11 Be sure to check out what they're offering during their segments.
01:13 It really helps support the show.
01:15 It's that conference time of year, everyone.
01:18 There's actually a bunch of interesting things happening around Python conferences.
01:22 So let's do a quick update.
01:24 First, we'll do this chronologically.
01:26 First, Pi Cascades is happening in Vancouver, BC, January 22nd and 23rd.
01:31 I'm going to be there.
01:32 So if you are one of the lucky people to have actually gotten a ticket before they sold out,
01:36 hope to see you there in Vancouver.
01:40 Next, we have Pi Colombia in Medellin, Colombia.
01:42 This is February 9th, 10th, and 11th.
01:45 So if you're in South America or want to go to South America and would love to go to this conference,
01:49 please check out the Pi Colombia conference.
01:52 Those guys are doing cool stuff down there.
01:53 Next up, PiCon Slovakia.
01:56 This is March 9th to 11th in Bratislava.
01:59 And I'm actually going to be speaking there and doing a workshop.
02:03 If you're in Europe and you can make it to Bratislava in March, that would be awesome.
02:07 Please come say hello or attend one of my talk show workshops.
02:10 And finally, the big one, PiCon US in Cleveland, Ohio, May 10th.
02:15 I personally just finalized all my travel plans.
02:17 I hope to see you there.
02:18 There's still tickets available.
02:20 They're not yet sold out as far as I know.
02:22 So hurry, hurry, because just like Vancouver, they will sell out.
02:26 We're also going to have a booth with lots of cool giveaway stuff there.
02:28 So please stop by our booth and say hello if you make it to PiCon US.
02:32 All of these conferences are amazing.
02:34 And I hope you can make at least one of them.
02:36 Now let's get to the interview.
02:37 Phil, welcome to Talk Python.
02:39 Thank you.
02:40 Hello.
02:40 It's great to have you here.
02:42 I'm a big fan of asynchronous programming.
02:44 And I consider myself really a web developer at heart.
02:48 And so this project that you are creating is really, really interesting to me.
02:54 Port, kind of an asynchronous version of Flask.
02:57 And we're going to get into all the details in that and really dig into it.
03:00 But before we do, let's start with your story.
03:02 How did you get into programming in Python?
03:03 So I got into programming to really to make games when I was a teenager.
03:08 The first one was specifically was to try and make my own version of Cannon Fodder, which
03:13 I quite enjoy playing around my friend's house.
03:15 So that's how I got into it.
03:16 And it was VB originally for me.
03:18 VB, like VB6 type of thing?
03:20 Yeah, I think it must have been.
03:22 Yeah.
03:22 Or like the Microsoft Visual Basic, right?
03:24 Yeah.
03:25 Oh, yeah.
03:26 Very interesting.
03:27 That's a long ways from the web.
03:28 Yeah, definitely.
03:29 It is cool.
03:31 So you started out in Visual Basic and created that.
03:34 And that sounds really fun.
03:35 Where'd you go from there?
03:36 Originally figured of doing computer science at university, but I did physics instead.
03:40 But over the time, I got more and more into coding.
03:43 And by the time I did my PhD, that was quite heavy on the simulation parts.
03:47 And later on in the postdoc, I switched, roughly speaking, from C++ to Python.
03:53 So I'm reasonably new to Python.
03:55 But that's the kind of overview of how I progressed in the languages.
03:59 Oh, yeah.
04:00 Really cool.
04:00 What was your PhD focus?
04:02 It was background rejection in neutrino experiments in Canada.
04:05 And the simulation of the experiment itself.
04:09 So a lot of physics simulation.
04:10 Oh, yeah.
04:10 That's cool.
04:11 And of course, computation is really front and center in that kind of stuff, right?
04:15 You can't really explain it anymore without the Monte Carlo simulation.
04:19 It's too complex.
04:20 Yeah.
04:20 Monte Carlo simulations are really amazing.
04:23 They seem like magic to me.
04:24 Here's the thing that'll take a month.
04:25 Oh, but we can do it in five minutes.
04:27 Very cool.
04:28 If you're willing to accept a little uncertainty, we can do it really quick.
04:30 All right.
04:31 That's cool.
04:31 So what do you do day to day?
04:33 Not still physics simulations, right?
04:35 No.
04:35 I left physics about three years ago now.
04:38 I work for a company called Smartkits in London.
04:40 And they're a betting exchange or event trading exchange.
04:44 So ideally, we become like the prediction market.
04:47 So you go to figure out who's going to win an election, for example.
04:51 That's what we want to be.
04:52 Oh, that's really cool.
04:53 So probably a lot of website traffic.
04:55 Do you have APIs and stuff people can consume?
04:58 Exactly.
04:59 So we build the website and the API to interact with the exchange to allow people to trade.
05:03 So yeah, it's quite heavy on Python.
05:06 There's some JavaScript and some Erlang as well.
05:08 Erlang.
05:08 Okay.
05:08 Very cool.
05:09 That sounds like a fun thing to do day to day.
05:11 And it's not that far removed from this project we're going to talk about.
05:14 So I kind of want to start the conversation and set the stage by just talking about
05:21 asyncio in general.
05:23 So this was introduced in what?
05:26 Python 3.4, right?
05:27 I think so.
05:28 Yeah.
05:28 I think it was the Tulip project before then, which you could use with Python 3, I think.
05:32 So yeah, I think it came in the standard library in 3.4.
05:36 All right.
05:36 So what's the main idea behind Async IO?
05:38 I think it's about trying to utilize the CPU as much as you can.
05:43 So instead of just being idle while you're waiting for IO, you've switched to something
05:47 else.
05:47 You gain the concurrency that way.
05:49 To me, that's what it's about.
05:51 Yeah.
05:51 I think one of the real interesting things is web requests are so often waiting on other
05:58 things.
05:59 Like at the web server level, at the web framework level, a request comes in and it says, hey,
06:05 I'm this user.
06:06 I care about this data.
06:07 What's the very next thing you do?
06:09 You either call a microservice.
06:11 You maybe find it on disk or you go talk to a database.
06:14 Regardless, that whole process is just going, and I'll just be here for when you need me
06:20 waiting for you to get back to me on that data so I can give it to the user, right?
06:24 Yeah, exactly.
06:24 And so if you could somehow say, well, let's pause that until that bit of work has an answer
06:31 back from the Async IO, from the IO conversation, right, to the database or whatever, and let some
06:40 other part of code that's going to run, run, so it can then begin to wait, you know, a lot
06:45 of hurry up and wait on the web server.
06:46 And so the Async IO basically means if you're waiting on IO, that same thread can be processing,
06:52 can release the GIL and be processing other things, which I think is especially important
06:57 in the web world.
06:58 I think it goes a bit further as well.
07:00 Like if you compare it to say threading, which is another way to achieve the same thing in that
07:06 it's obviously a coroutines are much more lightweight.
07:09 So you can handle many more of these requests at any point in time than you can with threads.
07:15 So that's the other part of it that makes it really useful for web servers.
07:19 Yeah.
07:20 The lightweight part is really, really interesting.
07:22 I mean, anyone who's sort of worked with a generator, it's kind of, kind of like that.
07:27 And threads themselves have all sorts of overhead that comes with them, creating a thread, destroying
07:32 a thread.
07:32 So maybe you put them into a thread pool, but threads themselves, they have context switches
07:37 at the kernel CPU level.
07:39 They may mess up the cache, right?
07:42 The L1, L2 cache.
07:44 And so then they kind of can wreck performance as you cycle between the threads.
07:48 You know, the cost of a thread varies by operating system, but they can have like a meg of stack
07:55 space allocated to them.
07:56 There's all sorts of things that limit how many you can have.
07:59 So you can have a 10, no problem.
08:01 A hundred, no problem.
08:02 A thousand starts to, you know, maybe push the actual memory limits of your computer.
08:06 With these, the sort of asyncio stuff, you can have many thousands, which is, I think, really
08:12 awesome.
08:12 So maybe give us a quick comparison to this concept with like Eventlet or GeoVent.
08:18 How are they similar or different?
08:19 So I think Eventlet and GeoVent are the ones you typically use with Flask at the moment.
08:25 And I think Eventlet is roughly started out as a fork of GeoVent.
08:31 So they're similar in principle, those two.
08:33 And they're all three, including AsyncIO, are very similar in principle.
08:38 They're an event loop that runs tasks or greenlets or coroutines and allow yielding when there's
08:45 IO.
08:45 But I think the crucial difference and what AsyncIO, I think, does differently to all the
08:51 choices I've seen so far is that it makes the yielding explicit.
08:54 You have to write the await keyword.
08:57 And that fits the Python philosophy a lot better.
09:01 Obviously, explicit being better than implicit.
09:04 And I think it makes it a lot clearer to the user because you now know when these changes
09:10 are going to happen.
09:10 And you get a feeling that you're actually writing these yields in your code rather than
09:15 there's some magic in the background making it happen.
09:17 Right.
09:17 Exactly.
09:18 That's very cool.
09:19 So basically, anytime you're going to call one of these Async methods, you say await.
09:24 And that signals to the runtime to say, now we're going to give up this execution and pause
09:31 until this IO or this event completes.
09:34 Go on.
09:35 And you sort of really clearly know and call out where things are going to sort of pause,
09:41 at least for that request, but also yield the execution.
09:44 Right.
09:44 I think it was probably a little clearer before they introduced the Async and await keywords
09:49 because you'd write an explicit yield to actually yield and a yield front to just switch control
09:55 to your next coroutine.
09:56 Whereas just with the await keywords, you could await a coroutine or await something that does
10:01 yield, but it's not quite clear as much.
10:04 But still, you know that that line could possibly yield, which makes it a lot clearer.
10:09 Yeah.
10:10 Yeah.
10:10 That's really cool.
10:11 So one thing that you've talked about is this AsyncIO color problem.
10:15 What's that?
10:15 I can't remember who termed it as such.
10:18 Yeah.
10:19 I certainly copied their choice because you could imagine coloring certain colors red and the
10:25 red functions call other red functions and other functions black and the same applies.
10:30 And the red functions would be coroutines asynchronous functions and the black ones just use synchronous
10:35 ones.
10:35 So I think that's where the naming comes from.
10:38 But the basic problem part of it is that the explicit design forces you to only be able
10:44 to trigger coroutines asynchronous code from coroutines.
10:48 So if you have a synchronous function, your standard Python function, it's quite hard to run a coroutine.
10:54 Right.
10:54 And that's one of the hearts of the problems of why so many popular web frameworks don't
11:01 just enable async.
11:03 Right.
11:04 Because it comes in through this WSGI interface.
11:06 It calls, you know, the one function that is in that API and it starts out synchronous.
11:12 And so there's really no layer, no place where you can sort of inject that easily.
11:18 Right.
11:19 Not only is it viral in nature, as soon as you want to await something, you need to be in a
11:24 asynchronous in a coroutine to do so.
11:26 To also call that coroutine, you need to be in a coroutine again.
11:30 So you go all the way up to the event loop that inevitably runs your coroutines.
11:34 And WSGI, at least in its current form, doesn't have a concept of event loop.
11:39 So it's not having that.
11:41 It's also not going to do the IO necessarily in a way that's going to yield to any event loop
11:47 for you.
11:47 So even if you introduce it later on, you're not necessarily getting the full advantage
11:52 of yielding on the IO.
11:54 You basically have to block and wait to give the request back at some point, or you're going
11:59 to get ahead of yourself.
12:00 And that pretty much cancels out all the benefits that you had.
12:03 So your project says, we're going to take this and we're going to start from this asynchronous
12:09 level.
12:10 One thing I do want to call out, and I can't quickly look up where it is, but there's this
12:16 great article by Christian Medina that says, it's titled something like Controlling Async
12:22 Creep.
12:23 And he talks about this problem, this asyncio coloring problem, and different techniques
12:29 you can have to sort of create boundaries or whatnot.
12:32 Very, very interesting article around this idea.
12:34 But let's stay focused on this.
12:36 So maybe before we get into how people work with Flask, I feel like the Flask API is showing
12:44 up everywhere.
12:45 For example, I just recently did a show on Flask Ask, which is like a Flask API for writing,
12:51 say, Echo Dot type voice interactions.
12:55 And you just write it as a Flask app, and it just magically plugs into that whole framework
13:02 from Amazon, which is really cool.
13:03 So why do you think Flask is so popular?
13:05 That does sound really cool.
13:07 I think it's a very clear and concise API to use.
13:15 I think if you're a very simple example that hello world with the decorator saying this
13:21 is the root and this is the view function, I think you couldn't get an API clearer than
13:25 that for a web framework.
13:27 It's so concise and nice.
13:29 And I think the design choices Flask has made as a whole just really kind of emphasize this
13:35 usability.
13:36 So it might be the request object being a global, for example, just because you're going
13:42 to use it that way, it makes life so much easier.
13:44 And so I think this and the familiarity of that API is what kicked it off.
13:50 And then I think that enabled the really strong community around Flask that probably makes it
13:56 really popular today.
13:57 So the great blog posts you can find, especially effectively the large blog post I think everyone
14:04 starts with from, I think, Michael Grinberg and the extensions on Miguel.
14:10 Yeah, Miguel Grinberg.
14:11 And he just, he's actually redoing that one for Python 3 and modern Flask and all that.
14:16 So like he's halfway through redoing that, which is really exciting as well.
14:20 Yeah, I'll be sure to link to that.
14:21 I agree.
14:22 I think there's also so many plugins you can get to sort of extend Flask, right?
14:26 There's extensions for almost anything you could want to do.
14:29 Let's talk about this for a second.
14:30 What if I have a Flask app, standard Flask, not Quart or anything, frequency like that, and
14:36 I want to do something asynchronous in it?
14:38 Like, let's say I want to do WebSockets, which are basically permanent connections, right?
14:43 That can't easily be done synchronously in sort of a request response style.
14:48 So how would that go?
14:49 Can you do it?
14:50 Yeah.
14:51 I think typically at the moment you'd use Gevent, at least the two popular ones, which
14:55 is Flask Sockets, the popular extensions, and Flask Socket.io, I believe use Gevent under
15:00 the HUD.
15:01 And you do need the event loop really to make it possible.
15:05 And with those, it's actually quite easy.
15:08 I think you just add that extension.
15:11 It takes care of the rest for you.
15:12 And you can just decorate a function that you say is a WebSocket root and deal with the WebSocket
15:19 directly.
15:19 It's very easy.
15:20 I see.
15:21 So does it basically like hand it off to that processing to just run asynchronously on its
15:27 own?
15:27 Well, if we take the Flask Socket example, what that does is it'll wrap your Flask app
15:32 in a Gevent whiskey server and introduce the WebSocket at that level.
15:37 So a WebSocket request coming in would be handled before it gets to Flask, although it looks like
15:43 it's going to Flask, but it would be handled beforehand.
15:45 And then everything else would just go straight through to Flask with the whiskey interface.
15:49 So yeah, it would work that way.
15:52 Yeah.
15:52 Nice.
15:52 This portion of Talk Python is brought to you by SmartKits.
15:56 And they're looking to hire someone to write some really awesome Python code.
16:00 SmartKits operates a world-leading exchange for peer-to-peer trading on sports, politics,
16:05 and current affairs.
16:05 As a business, SmartKits is widely recognized as one of the fastest-growing tech companies
16:10 in Europe and has won a roster of awards for its success over the past few years.
16:14 Headquartered in London with a new tech hub in downtown LA, the company is pioneering a
16:20 self-managed organizational structure.
16:22 This breaks down the traditional pyramid of hierarchical silos into a more fluid and
16:26 flat network of interlinked teams who are engineers of the driving force.
16:30 They're no formal bosses.
16:31 Staff are allowed to set their own salary, have unlimited holiday, and work where they like
16:36 within the company.
16:37 Over 60% of the staff are engineers who work on a modern tech stack predominantly based on
16:43 Python, complemented with Erlang and JavaScript.
16:45 SmartKits uses Python 3.6 throughout and deploys dockerized microservices multiple times a day.
16:52 Apply to work at SmartKits by visiting talkpython.fm/SmartKits or just click the link in
16:58 the episode notes.
16:59 Suppose I want to have some Flask view method and it's going to say call a web service.
17:07 It's going to talk to a Postgres database, for example.
17:10 There are asynchronous ways to do those things, right?
17:15 If I have AIO HTTP client, that's a really nice AsyncIO friendly way to call remote services.
17:22 And I could use the Async Postgres driver there and call that.
17:28 But with straight Flask, I can't put like Async on my methods, right?
17:33 Or await in it and really get it to honor that, could I?
17:36 I don't think so.
17:37 There was an extension called Flask aiohttp, which I think is no longer maintained.
17:43 But that, I believe, used the WSGI interface that aiohttp provided, which I think is also
17:49 deprecated now, sadly.
17:50 But that would allow you to fairly easily, I think, make your Flask view functions async.
17:56 But it wasn't, I think it had some issues whereby the locals, the requests, gee, those request
18:04 locals would get corrupted or could get corrupted.
18:07 So I think that kind of ruled it out.
18:09 Yeah, that doesn't sound amazing, right?
18:12 No, no, you certainly don't want that.
18:13 There was also a fork, a Flask I found that did make the view functions async.
18:20 It was designed around async AO for 3.4.
18:24 But I haven't quite tried to figure out how it worked or if it worked, but it hasn't been
18:29 touched for about three years.
18:31 So I don't think that's maintained either.
18:33 Right.
18:33 And so this maybe brings us to your project, Quart.
18:37 All right.
18:37 What is Quart?
18:38 So Quart is a web micro framework, much like Flask, based on async AO and the Flask API.
18:44 So the aim really is to provide the easiest stepping stone from someone who has a Flask
18:50 app or Flask knowledge to use async in that app or in a new app with that knowledge, basically.
18:56 So yeah, it's just a web framework, web micro framework.
19:00 Nice.
19:00 So you decided instead of, because there are other asyncio frameworks that have come
19:06 around, Sanic, Chepronto, others that we can talk about later.
19:09 But generally they said, we're going to come up with a new way to program the web and we're
19:15 going to sort of do it around this asyncio thing.
19:17 And you said, look, Flask is super popular.
19:20 It has this API that people already know.
19:23 People can go and take Miguel's tutorial and learn about it already.
19:28 But you just want to have it work with asyncio, right?
19:33 And so we're going to start from there, right?
19:35 How much did you borrow from Flask and how much did you have to start from scratch here?
19:39 I've tried to borrow the entire Flask API and quite a bit of the Verkser API, which is
19:45 the part under Flask that powers most of the HTTP stuff.
19:49 I've tried to borrow most of that as well.
19:51 So yeah, a great deal, hopefully.
19:53 What I'd really quite like is if you have a Flask app that doesn't use any extensions, you
19:59 can just find, replace Flask with Quart and then add the async and await keywords and it
20:03 just runs.
20:04 That's what I'm kind of aiming for.
20:05 That's great.
20:06 And how close is it to that goal?
20:08 I think it's quite close.
20:09 I think it's really the details now.
20:12 So for example, I need to work on the e-tag handling, how they're applied to static files
20:18 and stuff like that.
20:19 And subdomain handling in the routing system.
20:22 I think those are real details that I think most of the use cases, it should be possible
20:27 to just do that now.
20:27 To just, as in, find, replace.
20:30 Oh, that's awesome.
20:31 You could go like grab Miguel's tutorial and just, I don't know, what's the verb of making
20:35 it run on Quart?
20:36 Quartify?
20:37 Add Quart capabilities to it, right?
20:39 That'd be cool.
20:40 Very cool.
20:41 I guess one of the first questions is why not just fork Flask and just tack on the little
20:47 bits of asyncio handling that you need?
20:50 That would be ideal.
20:51 Well, but at the moment, at least, it's beyond what I'm able to do.
20:56 And I think it goes back to the Whiskey interface, which really isn't asynchronous.
21:01 So, again, like if you really want the event loop to be able to get the yield on the request
21:06 IO and the response IO, you need to be controlling that part.
21:09 And so I think you have to go start from Flask, go up to VerkSug, and then almost go up to the
21:17 WSGI servers themselves and make them asynchronous to really, well, I think IO compatible to really
21:23 make it possible.
21:24 And, yeah, I could really make that work.
21:28 It was quite hard.
21:30 I know a lot of people have tried and they've talked about adding like a Whiskey 2 or various
21:39 acronyms with A involving in there to basically asyncify that API.
21:44 But there's really not a lot of flexibility in there.
21:47 And the actual Whiskey API, which is what all the web frameworks use to plug into the
21:53 various web servers, right?
21:54 I want to run on MicroWSGI.
21:56 I want to run on Genicorn or whatever.
21:58 They all speak Whiskey.
21:59 So you can just plug Pyramid, Flask, Django, whatever in there, right?
22:03 One of them I was looking at is ASCII, which I think Django is pushing.
22:07 So I think the idea there is you just push out the messages to a queue and then you have loads
22:13 of things consuming it in an asynchronous fashion and then returning the results.
22:18 I don't think it would quite work for Quart either, but they seem to want to.
22:22 I think they're trying to suggest that as the next step for Whiskey.
22:25 Like it becomes ASCII.
22:26 Yeah, yeah.
22:27 That's one of the acronyms I was thinking of.
22:29 So how do you add on things like WebSocket support or HTTP2 pipelining where you can make
22:37 a request and actually through that in single network request punch like three CSS, a JavaScript,
22:43 an image, an HTML file response as one.
22:46 Do you think they could make that work there?
22:48 I think so.
22:49 Yeah, because each request could just be a separate message on their queue and then something can
22:54 consume and produce the output and send it back.
22:57 So I'm sure it would work for them.
22:59 Maybe you could somehow bundle up the multiple responses or something at the framework level
23:04 and then send it over to the network.
23:06 I don't know.
23:07 It's yeah, it's going to it's going to be pretty interesting with all the HTTP2 stuff coming
23:11 along and whatnot.
23:12 Oh, definitely.
23:13 I think HTTP2 is one of the most more exciting things I'm interested in.
23:17 And I'm very pleased that Quart can do it.
23:20 So it's very good to play with.
23:22 It can.
23:22 That's really cool.
23:23 So how much of that is the framework and how much of that is, say, just the server it's
23:27 running on?
23:28 Say if you run on G Unicorn, right?
23:30 How much is G Unicorn doing versus how much of is Quart actually doing to make the HTTP2
23:35 supported?
23:36 In a traditional sense, the WSGI server does all the HTTP passing and just passes through
23:40 the environment.
23:41 But for Quart, how it works with G Unicorn is we just use the socket.
23:46 So G Unicorn doesn't actually do any HTTP passing.
23:50 It all goes through to Quart.
23:51 So all the HTTP2, the HTTP1, it's all taken care of in Quart.
23:55 And we just pipe it back out through the socket that G Unicorn's provided.
23:59 Give us a sense around the kind of performance differences that people could expect.
24:04 If they, say, have a standard Flask app running on G Unicorn now, if they flip to using Quart
24:10 also on G Unicorn, what do you think happens?
24:13 I actually did a, I think you're going to link an article I did to look at this.
24:17 And I looked at a kind of production use case where you have a simple CRUD app with a database
24:24 in the background.
24:25 I used G Unicorn with eventlet.
24:27 So it was asynchronous, fairly similar type of work load pattern and the way it approaches
24:34 the problem.
24:35 And I compared it against Quart.
24:36 And I think with Quart, I got something like a free time throughput speed up.
24:41 So the latency or the request time itself didn't really make too much of a difference.
24:47 But the amount of requests it could handle at once really increased.
24:50 Yeah, that's really cool.
24:51 And I would guess that you're probably were not working with like a super slow database
24:57 or a tremendous amount of data in your simple test.
25:00 But it seems to me like the worse the database performs, the more beneficial having this asynchronous
25:07 thing could be to sort of free up the waiting.
25:11 The waiting, the worse the waiting is, the more beneficial is to have async methods.
25:16 Although that should apply to both sides of the test because eventlet would do the same
25:21 in the flask sense.
25:22 Right.
25:23 OK, yeah.
25:23 So if you're already switching to eventlet, then it would.
25:27 Have you thought about this relative to a non-async flask, like just a standard flask, if you're
25:34 not using eventlet?
25:35 Yeah, it makes a huge difference then because your requests effectively become synchronous.
25:39 You have to do one at a time.
25:41 So unless I suppose you used threading.
25:43 I haven't tried threading, but assuming you've got no asynchronous aspect at all, then it would
25:48 make a very large difference because you would have to finish the one request before you could
25:52 even start the next.
25:52 That's for sure.
25:53 How interesting.
25:54 OK, so you talked about flask and I said one of the benefits of it is these flask extensions,
26:01 right?
26:01 There's a bunch of stuff you can plug in.
26:03 Does Quart support flask extensions?
26:06 Supports a good fraction of them, I think it's fair to say.
26:10 So this goes back to that color problem we mentioned earlier, because, of course, all the
26:15 extensions are synchronous.
26:16 They have synchronous functions.
26:18 And inevitably, they're going to try and call some code that's now asynchronous in Quart.
26:23 And that's where it becomes a problem.
26:25 So I can go in the details of how it gets around that.
26:29 But that's why some of them work.
26:31 Yeah, that'd be kind of interesting, right?
26:33 Because people are going to pip install these extensions and generally not change the code.
26:37 And until your framework is popular enough that people are creating sort of async equivalents
26:44 of their extensions, you have to make these, you have to blend the colors, right?
26:48 So how are you doing that?
26:49 It's quite fun to look at.
26:50 It took quite a bit of time.
26:51 So if I go back to how you run a coroutine, you need to run it in the event loop.
26:56 And if you're outside of the event loop, it's quite easy.
26:58 You just either create or get an event loop and tell it to run a coroutine.
27:02 And it's all very good.
27:03 But if you're in a function that's been called by something within the event loop, you can't
27:08 do that.
27:08 The actual async arrow function run until complete, for example, will refuse to run because you're
27:14 inside the event loop.
27:15 And it turns out this was known back during the tulip days.
27:20 And someone proposed a solution to this, which was to actually run the event loop again within
27:26 the event loop to run this particular coroutine.
27:29 And I think it was rejected because it just made things quite complicated and wasn't a particularly
27:35 useful use case.
27:36 But in this case where you've got legacy, well, you have synchronous code, trying to call
27:41 asynchronous code is exactly what you need to do.
27:44 So what Quart will do if you want to go down this route is monkey patch the event loop to
27:49 add a method to run the event loop, if you like, manually for a coroutine that you specify.
27:56 So it will suspend the event loop it's already in, run it itself, and then restore everything
28:01 after it's complete.
28:02 So it probably sounds a bit messy.
28:04 So it probably is.
28:07 It sounds like it could create this cascading chain of nested event loops going down and
28:12 down and down.
28:13 Yeah, probably so.
28:14 But it does allow like these extensions with a synchronous call to another function to be
28:21 able to call an asynchronous function without them being able to tell.
28:24 There's just a layer in between that does this kind of mapping from sync to async.
28:28 That's really cool.
28:29 I'm very creative.
28:30 So you said it works for most of them.
28:33 When it fails, what happens?
28:34 Like, why does it not work?
28:35 Do you remember?
28:36 So Flask has these request locals, like the request object or the G object.
28:41 And when you try and do anything with them or access any attribute of them, it effectively
28:46 proxies that action to an instance that's local to the thread or greenlit that you're on.
28:53 And I hope that makes sense.
28:54 It's proxying, basically.
28:56 So what I can do in Quart is I can, during that proxy, also take it from a synchronous call to an
29:02 asynchronous call.
29:03 So that works really well for these local proxy objects.
29:07 The problem is when an extension uses the Flask class itself, like the app, because I haven't
29:13 figured out a way yet to effectively proxy the call and convert it from synchronous to asynchronous.
29:18 So if the extension just uses these globals, it's good.
29:23 If it uses the app, I need to be a bit more clever.
29:25 Hopefully you get that puzzle figured out, because it'd be really cool to have that supported
29:29 there.
29:29 Very nice.
29:30 So what template languages are supported?
29:33 You have Jinja 2?
29:35 It follows the Flask design in that respect and just does Jinja 2.
29:38 But I'm sure like the, I think there's an extension called Flask Maco.
29:42 It's quite popular.
29:43 I think that may work as well.
29:46 The question becomes whether that template in engine is asynchronous itself.
29:50 So if it isn't, you just get a bit of a performance hit.
29:53 Right.
29:53 I see.
29:54 So the Jinja 2 one supports asynchronous behaviors in directly?
29:59 It does.
29:59 It's actually, if you look at the code, it's kind of amazing how they do it.
30:03 So if you're running Python 3.6 and you ask it to be asynchronous, it will patch itself to
30:08 add the asynchronous methods.
30:10 It's, yeah, it's really quite good to look at.
30:12 Wow, that's quite awesome, actually.
30:14 I didn't realize it did that.
30:15 So I'm guessing you don't support Python 2 in this.
30:19 Is it Python 3 only?
30:20 It is.
30:21 It's even more restrictive than that, actually.
30:23 It's Python 3.6 only because I use asynchronous generators.
30:27 One of the uses being if you want to stream a response, you want to yield data back whilst
30:32 being asynchronous.
30:33 So because of that, you can't use anything other than Python 3.6 where they were introduced.
30:37 Yeah, that's really interesting.
30:39 So I feel like a few years ago, the story was, well, people can't move to Python 3 because
30:44 there's all this cool stuff that we're using that only supports Python 2.
30:48 And now I think more and more we're ending up in a situation where it's there's all these amazing new things.
30:54 But they're only accessible to you if you're using the latest versions of Python 3, which
30:58 I think that's a good move.
31:00 The other thing it does as well, which really constrains it to Python 3 or I've used is I
31:04 type into everything, including the variables, which the syntax, well, the nicest syntax was
31:09 only introduced in Python 3.6.
31:10 So yeah, I quite like that as well.
31:13 I really do as well.
31:14 I find it makes the editors much smarter about the types of things you're actually working
31:18 with.
31:19 You can run tools and say, no, no, you're passing an answer.
31:21 You're supposed to pass the whole object, not the ID of the object here.
31:24 Things like that.
31:25 It's great.
31:26 This portion of Talk Python to Me has been brought to you by Rollbar.
31:30 One of the frustrating things about being a developer is dealing with errors.
31:33 Relying on users to report errors, digging through log files, trying to debug issues, or getting
31:39 millions of alerts just flooding your inbox and ruining your day.
31:42 With Rollbar's full stack error monitoring, you get the context, insight, and control you need
31:47 to find and fix bugs faster.
31:49 Adding Rollbar to your Python app is as easy as pip install Rollbar.
31:53 You can start tracking production errors and deployments in eight minutes or less.
31:57 Are you considering self-hosting tools for security or compliance reasons?
32:01 Then you should really check out Rollbar's compliant SaaS option.
32:05 Get advanced security features and meet compliance without the hassle of self-hosting, including
32:10 HIPAA, ISO 27001, Privacy Shield, and more.
32:14 They'd love to give you a demo.
32:16 Give Rollbar a try today.
32:17 Go to talkpython.fm/Rollbar and check them out.
32:21 Yeah, so actually converting your Flask methods to Quart methods is quite easy, right?
32:32 You just, you have your, let's say, like a review function.
32:35 I think this is an example you have on your website.
32:36 You can say def add review data equals request.getJSON.
32:40 Or you can just say async def add review data equals await request.getJSON.
32:46 And you just add the async and await keywords and off it goes, right?
32:50 Yeah.
32:50 So I think it wouldn't be that hard to convert a small to medium-sized Flask app to Quart.
32:57 What do you think?
32:57 How much effort and testing has to be done?
32:59 I think you're right.
33:00 I think small ones should be quite easy.
33:03 What probably make it hard for people is that they're probably going to call something else,
33:08 like you were saying earlier.
33:09 If you're going to do a web request to a, or a PHP request to a microservice or something
33:14 to a database or something like that.
33:16 And those libraries are likely to be synchronous rather than async.
33:20 And while this comes back to the color problem, you're going to have to switch from, say,
33:24 psycopg to asyncpg or your py Redis to like aioredis or requests to aiohttp.
33:32 And that's probably going to be more work because those APIs are suddenly different usually.
33:37 I would think probably the Redis wouldn't be that hard.
33:40 Probably the AIO HTTP client, not such a big deal.
33:43 But I feel like when you get down to database stuff, that's where the complexity lives a lot
33:49 of times.
33:49 So maybe upgrading the database driver or package to be the async one is probably where it's
33:56 most challenging.
33:57 I don't know.
33:57 Have you tried?
33:58 Have you got some experience?
33:59 Yeah, I've played along with myself.
34:03 And yeah, it's because I typically use psycopg directly.
34:07 I don't use SQLAlchemy on top of it usually.
34:10 And that's not too bad.
34:12 It's fairly easy to change.
34:13 And it has a big bonus.
34:16 Like there's an asyncpg.
34:17 If you look at how MagicStack report the benchmarking for it looks much, much quicker, which is excellent.
34:24 But yes, if you have a lot of ORM stuff, I think you're almost stuck.
34:28 I'm not sure I've seen an ORM that's async yet.
34:31 I know that SQLAlchemy hasn't done it.
34:35 I feel like there is one out there.
34:39 And I'm hesitant to say which one I'm guessing it is because if it's wrong, I'll try to put
34:46 some notes in the show notes or something.
34:47 I've seen one that I thought allowed it.
34:50 But yeah, certainly SQLAlchemy doesn't.
34:52 And that's unfortunate, right?
34:55 So it kind of says, look, if you're going to do the async stuff, then you're kind of stuck.
35:00 I guess there's a few things you could do.
35:04 You could move your requests out, say like another thread that you wrap up and you await
35:10 that thing's response or something.
35:12 But it definitely makes it not easy, right?
35:15 Yeah.
35:15 So asyncio makes that bit reasonably easy.
35:19 I think there's a function called running executor, which will do almost exactly what you just said.
35:23 So yeah, that's probably what you'd have to do.
35:25 Okay.
35:25 So I found what I was looking for and it's actually not the ORM itself.
35:29 It's an add-on.
35:29 It's called P.
35:31 So the ORM is PeeWee, which is a sort of a small ORM type of thing.
35:36 Right.
35:36 It's, it's pretty popular.
35:37 It's got close to 5,000 stars, but there's PeeWee dash async, which is an asyncio interface
35:44 for PeeWee.
35:45 So that one, that one you can do it.
35:48 So you can basically say await objects.create or things like that, where you basically do
35:55 the queries and then you can await the response, which is kind of cool.
35:58 Oh, that is cool.
35:58 Yeah.
35:58 I didn't come across that.
35:59 Yeah.
36:00 But it's unfortunate that it's not like the most popular one, like SQLAlchemy or say the
36:05 Django one or something like this, right?
36:07 Like it's, yeah.
36:09 But if people are looking for it, like maybe you'd have to switch to PeeWee async if right
36:13 now it's written in say SQLAlchemy.
36:14 I don't know.
36:15 So what do you think?
36:16 I'm thinking about this database problem.
36:17 Like, can you take a SQLAlchemy Flask app or a MongoDB Flask app and somehow shoehorn
36:25 it in so you can still do these queries?
36:27 You don't have to completely rewrite your data access layer, but make it async friendly.
36:32 Easy?
36:33 Not easy?
36:33 I think it, in all honesty, it requires a bit of effort.
36:36 Yes.
36:36 You'd have to, you'd probably want to choose a different driver.
36:40 So you need to spend some time looking into it.
36:42 And then you'd probably have to rewrite a bit to actually make it work.
36:45 So yeah, I don't think it's all that easy.
36:47 It doesn't sound easy to me either.
36:49 Although it may be worth it.
36:50 It's definitely not going to be just a throw an async and a wait keyword here and there and
36:55 just go with it, right?
36:56 Indeed.
36:56 I think it is.
36:58 It does look to be worth it.
36:59 If you, there's some articles by Magic Stack and the way they talk of the performance they
37:05 can get through the async stuff.
37:06 It looks really, really good.
37:08 Really very fast.
37:09 Yeah.
37:09 I think it's really quite powerful, but it also probably depends, right?
37:13 Like you guys, I suspect have a lot of traffic.
37:15 If you do 10 requests a second on your server or one, maybe just leave it alone, right?
37:24 Pay $5 more and get a bigger server and just be done with it.
37:27 There's probably some threshold where below that it's not worth rewriting it until there's
37:32 enough demand.
37:33 What do you think?
37:34 Certainly, yeah.
37:34 It's, even for us, it's not necessarily worth it to wholesale replace our services with core.
37:42 I think we're only really kind of experimenting with async O systems at the moment.
37:47 Cool.
37:48 So let's compare this a little bit with some of the other, what I consider the Python 3 async
37:53 web frameworks.
37:54 So we've got Sanic.
37:57 We have Jepronto.
37:59 AIo HTTP.
38:01 Like, how do you see your work similar or different to these?
38:07 There's kind of two approaches have been taken here.
38:10 And most of the Python 3 async ones I've seen have been more the micro framework style than
38:16 the Django style.
38:17 I think there's the ones that are Flask-like, which are Sanic and Jepronto, I think.
38:24 And then there's AIo HTTP that I think for very legitimate reasons by design doesn't want
38:30 to go down the Flask approach.
38:31 So I think that's basically the first choice.
38:34 And then I think Quart fits in because it's not Flask-like.
38:38 Hopefully, it is the Flask API.
38:40 So I think that sets it aside and it's kind of the motivation for it.
38:45 So then I think there's kind of that scale.
38:47 And then there's the what is the aim of the project in that scale.
38:50 So Sanic and Jepronto, I think, are all about speed as far as I can understand what they're
38:56 aiming for.
38:56 They're really about trying to get performance out of it.
38:59 And I think AIo HTTP is meant to be kind of like an all-encompassing HTTP library, right?
39:06 So it does the client side as well as the server.
39:08 So I think there's these varying kind of aims for the projects.
39:12 Right.
39:12 And I think it is a massive advantage to say we're not going to create something Flask-like.
39:17 We're going to create Flask effectively, but async-enabled natively.
39:22 And one of the main benefits there is obviously people can migrate more easily to it.
39:27 You already talked about the Flask extensions, all those kinds of things, right?
39:30 To me, it's mostly about not having to learn something new.
39:33 So I did play with Sanic.
39:35 I quite like Sanic, but I didn't want to have to learn how Sanic did things.
39:38 Yeah, exactly.
39:41 And if you want to go on Stack Overflow and ask, how do I do this with my web framework X?
39:46 You'll have way more answers if X equals Flask.
39:49 It's true.
39:49 Definitely.
39:50 Yeah.
39:50 Just the more tutorials, more courses, et cetera, et cetera.
39:53 Although I suspect there's minor differences.
39:55 The majority of it is kind of still the Flask API, the Flask story, right?
39:59 Certainly, there is differences in the details and obviously async.
40:04 Yeah.
40:04 So what are the hangups people might get trying to switch to court?
40:07 Are there things they have to be on the lookout for just due to the async nature or how careful
40:14 they got to be?
40:15 I think one of the most annoying parts is if you have a coroutine.
40:21 Say you want the example you said where you await the request.getJSON.
40:24 If you forget to write the await word, then the data, let me start again.
40:29 If you have data equals request.getJSON in your Flask and you change it to Quartz, that line
40:35 of code will still run.
40:36 It's just that data would now hold a coroutine object instead of the data you're expecting.
40:41 And that could probably catch you up quite easily because until you try and use it, you
40:46 don't know that it's going to be something different.
40:48 What do you mean status code doesn't exist anymore on this thing?
40:52 Like, why did this start failing?
40:54 Why are the attributes missing, right?
40:55 Of course, because you didn't await it.
40:56 Yeah.
40:58 Interesting.
40:58 So let's talk about the deployment story.
41:01 You talk a lot about using G-Unicorn.
41:03 Is it, you said you can basically run both Quartz and Flask apps on top of that in more or
41:08 less the same way, but just different config settings, right?
41:10 Yeah.
41:11 You just change the worker and that should be enough.
41:15 Nice.
41:15 Yeah.
41:15 So you say eventlet is the one to use for Flask and Quart-UVloop for Quart.
41:22 UVloop's pretty interesting.
41:24 Why don't you tell people about that?
41:25 My understanding, which I think is wrong, is that the kind of history goes from libEvent
41:29 to libEvent, which is what eventlet and gvent, I think, are based on, to libUV.
41:34 And they're all improvements on the previous iteration.
41:38 And UVloop is the kind of Python bindings to change the async loop policy to use UVloop
41:46 instead of the default one.
41:47 And the reason you'd want to do this is because it looks like, from all the stuff that's been
41:52 published, that UVloop really does make things run a lot quicker.
41:55 And if I understand it correctly, it's the same kind of base event loop that Node.js uses.
42:03 So it's got some proven track record of being very good.
42:07 So yeah, if you switch it to the UVloop worker, you can really get some performance boosts.
42:13 I get the same impression reading around that UVloop definitely speeds that up.
42:17 And it's cool that it's the same one that Node.js runs on because Node.js, for all of its flaws
42:22 and challenges, is definitely good at handling lots of requests based on IO completion waiting
42:29 behaviors, right?
42:30 Yeah, definitely.
42:30 I find it kind of amusing if I've understood the history right, is that UVloop is a rewrite
42:35 of EV, LibEV, LibUV is a rewrite to work on Windows.
42:40 At least that was the original motivation.
42:42 But as I understand it, the UVloop kind of bindings doesn't work on Windows.
42:46 So I find that kind of like sad irony.
42:49 That is very ironic.
42:51 It's turned its back on its origin.
42:54 How interesting.
42:56 So have you talked with Armin Roedeker, the guy who maintains Creative Flask, about what
43:02 you've done, how it maybe could be contributed back to Flask, what those guys are up to, things
43:08 like that?
43:08 I haven't, no.
43:09 I was actually hoping, because I think you've spoke to him on this program.
43:12 I was going to ask if you could introduce me, which would be excellent.
43:15 Yeah, I'd be happy to.
43:16 Sure.
43:17 Yeah, I don't really know what's going on in official Flask around all this stuff.
43:22 So it seems like you've got a really nice head start.
43:25 So that's cool.
43:26 I don't know what's going on officially for Flask either, but it doesn't look like they're
43:29 actively pursuing AsyncIO at the moment.
43:31 It looks like they're pursuing the release one of Flask, as far as I can tell, which would
43:37 be great as well.
43:38 Yeah, it would be great.
43:38 I suspect making major changes to Flask in terms of APIs and stuff is a slow, tedious,
43:46 careful process.
43:48 Same thing for Django and for Pyramid.
43:50 These are well-established, long-living web frameworks.
43:54 Compatibility is probably a top concern.
43:57 And I think you probably have to make some difficult changes to introduce AsyncIO as well.
44:02 So I guess my hope would be that Quart proves that this is desired and useful.
44:07 And then there's more kind of desire to actually make some Flask changes and to be able to
44:13 merge the two.
44:13 Yeah, then you'll push a major, major pull request over to Flask.
44:17 Well, it would have to be Flask and Virgisk, I think, if it's ever going to be possible.
44:22 Yeah, yeah, for sure.
44:23 Yeah, very cool.
44:25 All right.
44:25 So where are you going in terms of the future for Quart?
44:29 What's next?
44:30 I said earlier, there's details about the API that don't fully match at the moment, which
44:35 obviously I need to get correct.
44:37 So like little details about how e-tags are used and how static files are served, that
44:42 kind of thing.
44:42 After that, I want to kind of like really demonstrate the robustness of the HTTP2 handling and the
44:49 WebSocket handling.
44:50 So luckily, there's some compliance testing projects out there, which I can use for that.
44:56 And then it's also about the development process.
44:59 So Flask and Virgisk have this really nice kind of debug web page that tells you what's
45:03 going wrong when you try and do something.
45:05 I think Quart needs something like that as well.
45:06 Sounds very cool.
45:07 One of the areas of performance that we spoke about was with Async and Await and sort of
45:13 the asynchronous view.
45:15 We also talked about HTTP2.
45:18 How do you see that affecting performance?
45:20 Do you see like the ability to have HTTP2 also adding like another layer of speedups?
45:25 I do see that it makes things quicker when I test it.
45:29 I haven't published anything yet because I haven't really figured out what a kind of safe
45:34 comparison benchmark is because the cost of opening connections is quite high.
45:38 Maybe you should include that.
45:40 But you could effectively pipeline your maybe 20 connections down one and get a big difference.
45:47 But whether that's fair or not, I'm not sure.
45:49 So I don't know how to talk about it in a fair way.
45:52 It's really precarious to publish benchmarks.
45:56 Yeah, definitely.
45:57 No matter what you do, there's always someone that's going to show you how you're wrong.
46:02 And usually what that means is you're wrong because I use it this way and you're measuring
46:09 it that way.
46:10 And if I used it the way I use it, it wouldn't give you the same results as the way that you
46:15 use it.
46:15 And so your results are misleading or whatever, right?
46:18 And it's just really hard to get something truly representative of what people are doing
46:22 when they're doing all these different things, I think.
46:24 One thing I'd really like to do if I could convince my colleagues is change one of our edge
46:28 servers to be HTTP2.
46:30 And then you'd be able to see quite a big difference in real use cases.
46:34 So that'd be excellent.
46:35 Yeah, well, let us know when it's out.
46:37 I'll definitely point people at it.
46:39 That'd be awesome.
46:39 It would be.
46:40 Yeah, yeah.
46:40 Very cool.
46:40 So one thing I noticed is you have your project on GitLab and a lot of people have their projects
46:46 on GitHub.
46:47 And you've talked about this a little bit.
46:49 So why GitLab over say GitHub?
46:51 Initially, it looks a bit silly because GitHub is way more popular than GitLab.
46:57 And projects tend to be judged by, I think, at least superficially to begin with, by the
47:02 kind of stars and forks they have, which is always going to be low on GitLab.
47:06 But for me, GitLab is open source, which sways a lot of it in my mind.
47:12 It certainly made a difference to the company I worked for when we were starting up, like
47:16 to have such a great open source project we could just use to begin with.
47:20 And the other thing I kind of like is its CI system is really nicely integrated.
47:24 And that works very well for Cort at the moment.
47:27 So it's very easy.
47:28 So Phil, if you want to work on Cort, what editor do you open up?
47:31 I use Emacs, although I think I use Emacs really badly.
47:35 I don't really add any plugins.
47:37 I tend to use it just in the terminal.
47:39 Very vanilla.
47:41 I went through a stage of having it reasonably optimized.
47:44 And I just reverted it all back to plain.
47:47 So yeah, I don't put too much in my editor.
47:49 I probably should.
47:50 But yeah, I just open Emacs and go from there.
47:53 All right, cool.
47:53 And in addition to Cort, which is pip install Cort, right?
47:58 What other notable high-PI packages are out there that you want to recommend?
48:03 I think I'll recommend two that I use in Cort.
48:05 I recommend Flacate, which has certainly caught loads of silly bugs and style issues.
48:11 I think mostly I use it to take away any discussion or uncertainty about the style.
48:17 And my-PI for the same reason of catching the bugs and to make sure the documentation is right.
48:24 Yeah, my-PI is very cool.
48:25 There's a lot of activity around my-PI right now.
48:27 It's really nice.
48:28 For sure.
48:28 All right, so final call to action.
48:31 People are interested in Cort.
48:32 How do they get started?
48:33 Are you looking for contributors to the project?
48:36 Things like that?
48:37 Absolutely, yes.
48:38 It would be great if people could give it a go.
48:40 Open issues or pull requests, merge requests of changes.
48:44 That would be absolutely excellent.
48:45 And hopefully it's easy.
48:47 It's just pip install Cort and give it a go.
48:49 Hopefully it's as easy as Flask.
48:51 It's like basically five-line quick start.
48:54 Yeah, that's really cool.
48:55 And you have a video that's now on YouTube from one of the PyCons, which I'll link to.
49:01 That's PyCon UK, right?
49:03 2017?
49:03 Yep, that's the one, yeah.
49:04 All right, so if people want to watch your video presentation as well, I'll be sure to link to that.
49:09 So yeah, it looks like a really cool project.
49:11 Thanks for creating it and coming on the show to share it with everyone.
49:14 Thank you for the insight.
49:15 Of course.
49:15 Talk to you later.
49:16 Okay, bye.
49:18 This has been another episode of Talk Python to Me.
49:21 Today's guest was Philip Jones, and this episode has been brought to you by SmartKits and Rollbar.
49:26 SmartKits is looking for talented Python developers to build amazing Python 3-based microservices.
49:32 Apply at talkpython.fm/SmartKits and level up your career.
49:37 Rollbar takes the pain out of errors.
49:40 They give you the context and insight you need to quickly locate and fix errors that might have gone unnoticed until your users complain, of course.
49:48 As Talk Python to Me listeners, track a ridiculous number of errors for free at rollbar.com slash Talk Python to Me.
49:56 Are you or a colleague trying to learn Python?
49:58 Have you tried books and videos that just left you bored by covering topics point by point?
50:02 Well, check out my online course, Python Jumpstart, by building 10 apps at talkpython.fm/course to experience a more engaging way to learn Python.
50:11 And if you're looking for something a little more advanced, try my WritePythonic code course at talkpython.fm/pythonic.
50:18 Be sure to subscribe to the show.
50:21 Open your favorite podcatcher and search for Python.
50:23 We should be right at the top.
50:24 You can also find the iTunes feed at /itunes, Google Play feed at /play, and direct RSS feed at /rss on talkpython.fm.
50:34 This is your host, Michael Kennedy.
50:35 Thanks so much for listening.
50:37 I really appreciate it.
50:38 Now, get out there and write some Python code.
50:40 I'll see you next time.