Learn Python with Talk Python's 270 hours of courses

#147: Quart: Flask, but 3x faster Transcript

Recorded on Thursday, Jan 18, 2018.

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.

Back to show page
Talk Python's Mastodon Michael Kennedy's Mastodon