#452: Top Quart (async Flask) Extensions Transcript
00:00 Have you heard of Quart? It's the fully async version of Flask created by Philip Jones,
00:05 who's working closely with the Flask team on these parallel projects. The TLDR version is that if you
00:11 want to take advantage of async and await and you're using Flask, you want to give Quart a
00:15 solid look. We've spoken to Philip previously about Quart. This time around, he's here to
00:21 share his top Quart extensions and libraries that you can adopt today. This is Talk Python to Me,
00:27 episode 452, recorded January 10th, 2024.
00:31 Welcome to Talk Python to Me, a weekly podcast on Python. This is your host, Michael Kennedy.
00:50 Follow me on Mastodon, where I'm @mkennedy, and follow the podcast using @talkpython,
00:55 both on mastodon.org. Keep up with the show and listen to over seven years of past episodes at
01:01 talkpython.fm. We've started streaming most of our episodes live on YouTube. Subscribe to our
01:07 YouTube channel over at talkpython.fm/youtube to get notified about upcoming shows and be part of
01:13 that episode. This episode is sponsored by Posit Connect from the makers of Shiny. Publish, share,
01:19 and deploy all of your data projects that you're creating using Python. Streamlit, Dash, Shiny,
01:25 Bokeh, FastAPI, Flask, Quattro, Reports, Dashboards, and APIs. Posit Connect supports
01:31 all of them. Try Posit Connect for free by going to talkpython.fm/posit, P-O-S-I-T.
01:37 And it's also brought to you by us over at Talk Python Training. Did you know that we have over
01:43 250 hours of Python courses? Yeah, that's right. Check them out at talkpython.fm/courses.
01:50 Philip, welcome back to Talk Python to Me. Great to have you here.
01:53 Thank you. Thank you for the invite.
01:54 We're going to talk about web things. I love, I love some web things. We're going to talk
01:58 about async. I love async. And we're going to talk about a lot of little cool things you can
02:03 plug into your web apps that make them more awesome. And we give a lot of people a lot of
02:08 things to think about. So yeah, let's, let's do it. So I guess quickly, before we get into talking
02:15 about Cort, maybe a little catch up on where it's been since the last time we spoke about that.
02:19 Give folks a bit of background on yourself for who don't know you.
02:23 Sure. Yeah. I guess probably in this context and maybe the people listening are probably known
02:30 for Cort and Hypercorn and helping maintain the pallets libraries like Flask and Werkzeug.
02:35 But well, outside of that, I guess in my professional career, I work as a
02:40 software engineer in London. I work for a company called Curaleaf International. And
02:45 quite interestingly, because it's not very well known, we're one of the few companies that do medical cannabis in the UK. Not many people know it's legal.
02:52 Yeah, I'm pretty.
02:53 Putting that aside, so I've worked there for about a year and a bit now. Before that,
02:58 worked at various other software firms. And then before that, I was actually a particle physicist
03:02 working on experimental in Canada in a deep mine, which was quite a good fun.
03:05 So you like your variety of your jobs, I can tell.
03:09 Yeah, I had a definite career shift.
03:11 Indeed. Just a funny sidebar, you know, cannabis was legalized here three or four years ago in
03:16 Oregon. There are more cannabis shops than there are Starbucks, than there are McDonald's. Like,
03:22 I could walk to four from my house in five minutes. It's nuts. Anyway, it's very different
03:29 than it was a few years ago, for sure. It's all fine, but just different. All right. So
03:33 Cort, like Flask, kind of, but very interesting. It's got some other capabilities. Tell people
03:40 about Cort. How's it relate to Flask? There's a lot of crossover. You and David Lord are working
03:46 pretty closely, it seems like, on things these days.
03:48 I started Cort about, I guess, seven, maybe a bit more years ago now. And the original idea was
03:54 make Flask async. At the time, I couldn't figure out how to adapt Flask at all in that direction.
04:00 So I thought, okay, I'll start by just re-implementing the Flask API using a
04:04 Async Await. And that's what Cort started out to be. Over the years, David and I have worked
04:10 to try and merge the two together. So the very latest change that's happened a few months ago
04:15 is we actually based Cort on Flask now. So there's a lot of shared code now. And for a long time,
04:21 Cort was based on Werkzeug. And if you're familiar with the Palettes and Flask ecosystem, Flask is
04:26 built on Werkzeug. And Cort was, but now Cort is built on Flask, which is built on Werkzeug. And
04:30 they're very, very similar. Yeah, that's fantastic. Because the more that you can share,
04:34 like the more you all can just focus on features and making it better rather than
04:39 duplicating that effort. The other aim is that the APIs, well, the same as they can be,
04:44 other than async await. So you can take your Flask code base and move to Cort just by
04:48 adding async and await. That's the dream, even closer than it's ever been to that now.
04:51 You're pretty close to be able to rename the word Flask, keeping the case sensitivity intact from
04:57 Flask to Cort. And it works, right?
04:59 Yeah, in simple projects. Yeah. The more complicated ones, it comes down to the
05:03 extensions, which we're going to talk about.
05:05 Which is, yeah, that's the whole topic of today, right?
05:08 Yes, exactly. But yeah, that's the aim. Our long-term aim is to actually merge the two
05:13 and be able to say async Flask. Well, it's kind of the case now where you can say async Flask is
05:18 Cort and we can't quite do what Django has done and put async Django as it is in Django. Ours,
05:24 you have to make a naming choice from the start. But yeah, I think it's getting as close as we can
05:29 get. Maybe we'll figure out how to merge them in the future.
05:31 Yeah, it'd be great if it just, you could choose to use async def or just regular functions for
05:37 your views in Flask and not really think about it.
05:39 You can do that now.
05:40 Okay.
05:40 We've been able to do that for about two years, I think, trying to figure out when we merge that.
05:44 So you could use either, it's just not as performant. If your code base is going to be
05:48 mostly async, you probably want to switch to Cort fully because it'd be more performant
05:52 and more reliable and robust.
05:54 Sure. Why is that?
05:55 The Flask implementation will run your async code on a separate thread. So you get all that
06:00 overhead of switching between the threads, sending the information over and back, et cetera.
06:04 Right. Does it run like an asyncio event loop on the other thread, but then
06:09 we basically curry the results back and forth?
06:12 Yep.
06:13 And with Cort, it's running right on top of an ASCII server straight, right?
06:18 Yep.
06:19 Yeah. That's super cool. And you might be responsible for one of the more prominent
06:23 ones out there. Tell people about what is an ASCII server for people who don't know,
06:28 why does it exist? And then the one that you got.
06:30 Yeah, sure. So historically there was the Whiskey Standard, which separated servers from
06:35 frameworks applications and gave you a choice between how do you want to serve and deal with
06:40 concurrency and stuff? And then how do you want your API to build your apps, the framework to be?
06:45 I think it was a great choice because it made the ecosystem really, really nice for developers.
06:50 And ASCII is the same thing for async. And so Cort is an example of an ASCII framework
06:56 and Hypercon is an ASCII server. They originally together, much like Sanic started out as well,
07:02 but then I split that out Hypercon into its own thing. And then later on now Hypercon is also a
07:07 WSGI server. So yeah, in some respects, Hypercon is just a general purpose server for Python.
07:12 And so Hypercon, it lives in the same general usage basis, G-Unicorn and MicroWSGI, UWSGI.
07:22 Right. So I could run it in production in a kind of like a overseer mode where you can fan out
07:27 worker processes and system daemon, system D all that. That where it's at?
07:32 Yeah, it's exactly like that. Yeah. And it also in the ASCII space, it's similar to UVicorn or
07:37 Daphne. That's it. I think there's another, but I've forgotten the name just now.
07:41 Yeah. I was just messing with some Docker stuff and I was running the web app, a FastAPI app,
07:49 G-Unicorn using UVicorn workers. So handling the ASGI stuff that way. Is it better to use
07:56 Hypercon rather than something along those lines? A little more direct maybe? Or what do you think?
08:01 I think it's a bit of a choice. So how it differs from UV, well, UVicorn in general is quicker
08:06 because it's HTTP parser. HTTP 1 parser is quicker. And there's a few other choices that
08:11 tend to make it a bit quicker. However, this last time I checked, UVicorn doesn't support
08:17 as many extensions as Hypercon and it doesn't support HTTP 2 or HTTP 3. Now, most people don't
08:22 actually serve that directly. They tend to have a load balancer in front, but I tend to do that
08:26 because it's quite exciting. So you're getting there with Hypercon. And then after that, it's a
08:29 bit of a choice between what features do they both have, what do you want to use? So they're fairly
08:35 similar, I would say. Sure. Okay. Somewhat. I'll tell you what you want to choose, but for people
08:39 who have mental models, maybe using UVicorn, they could check out Hypercon, right? Yeah. So you
08:44 wouldn't need G-Unicorn because Hypercon has the process model that G-Unicorn adds to UVicorn.
08:49 And yeah, you can just switch over. It should be a one-liner because most of the time it's just that
08:54 one command line where you set this up, right? So yeah. Yeah. Very exciting. I'm going to give it,
08:58 give it a solid look because I'm deep in that world right now, playing with things, which is
09:04 very exciting, but not the topic for today. So the topic for today is people who are interested in
09:09 doing Flask and Quart, especially Quart, what are ways you can do more, right? So the frameworks
09:16 are pretty simple, right? That's of all the different APIs out there, the Flask API has
09:22 definitely won. You know, even though there's many different web frameworks, right? If you look at
09:28 the way programming works, you look at FastAPI, you look at a litestar, many of these things,
09:34 it's like you've got to create a thing called app and then you say app.get and so on. Right? So
09:39 this style of programming is really popular. I think it's because it's just straightforward and
09:44 simple opposite of Django, maybe not so many batteries included, but you can add the batteries
09:49 through extensions. Yeah. Yeah, exactly. I think that's always been the trade-off between Django
09:53 and Flask and perhaps even Django and Quart if you had to go Quart is Django makes the choice of
09:58 your batteries to a large extent, whereas Quart and Flask means you have to choose basically.
10:02 Yeah. Which is really nice if you got like, you know, like I would really rather use this graph
10:07 database for this thing. And I would really rather, you know, like you get a lot of choice,
10:11 but you have to, you're going to have to build a little bit more yourself and you've got to decide
10:14 more yourself. So, but with all these extensions, it starts to become easier to click the Lego
10:20 blocks back together. Yeah. Yeah, exactly. Yeah. All right. So if you go people listening, go to
10:26 quart.palettesproject.com and go in there and find the extensions. There's a great long list
10:33 of extensions that we've got here that they can choose to run, right. And how to do that and so
10:38 on. But you picked out a couple that you really want to highlight and we'll dive into them. And
10:42 if there's extra time, maybe we can, you know, pick some more out of the list and just see what
10:47 we can make of them. I think the best one to start with is a bit of discussion around an extension
10:52 called Quart Flask Patch, which is a bit of a odd name and maybe need to think of a better name for
10:57 it. But the idea behind this extension is it patches Quart at import time to look very much
11:04 like Flask. So you can use some Flask extensions directly. So this is again, the idea to make it
11:10 easier if you are so willing to transfer from Flask to Quart, this would allow you to an easier
11:16 step because you could add this one line in Quart Flask Patch and some of your Flask extensions
11:22 would just work. Now you've changed to Quart. That's awesome because there's a bunch out there.
11:28 So what happens when I run this? Is it basically say like, okay, there's a bunch of module namespaces
11:33 called Quart this, Quart that. Let's create proxies called Flask that just delegate into the Quart
11:39 elements and expose that back. So code written against Flask dot whatever, I think it still
11:44 exists. Yeah, exactly that. Yeah. It does slightly more because what it also needs to do is,
11:48 is because a lot of the Quart APIs are async. It needs to wrap them in a wrapper that allows it to
11:54 be called synchronously, but it needs to do so in a nested sense. And I don't know whether you've
11:59 looked much into like nesting event loops in one another or getting back to the same event loop.
12:05 It's a bit of a, I think it's fair maybe to call it a bit of a hack, but it does work well enough
12:12 to give you, to allow you to buy that time to develop on, but that's what it has to do. Yeah.
12:17 Yeah. I really wish that Python had an ambient event loop that was the default. This like,
12:24 give me an event loop. Oh, it doesn't exist in exception. Oh, creating an event loop. Sorry,
12:28 exception. It already exists. Like, you know, there's like, it's just really tricky to figure
12:32 out kind of deep down in some library. Like, well, am I in charge of this? Is it already going? And
12:37 I got to be part of something bigger. If you could just say, just async run this, you know, and just
12:41 you do that. You need to make an event loop, make it. If you don't need to make one, just put it in
12:46 the one that's already there. Right? Like I really wish there was a little bit cleaner story there,
12:50 but you know, we're not going to solve that with Quart extensions.
12:53 No, no. I mean, if they hadn't done that, it would have made making Flask async a lot easier.
12:59 You've probably come across the library. I think it's greenback.
13:02 Greenback. No, this is new.
13:03 So greenback is a green, like stack switcher based library that allows you to get back to
13:12 the event loop when you're deep in some sync code, which is quite nice in the sense that, you know,
13:18 it allows you to do all the stuff that's difficult, but it's not so nice in that it's kind of hidden
13:23 in a way monkey patching type stuff going on in the background.
13:26 Yeah. Yeah. Yeah.
13:27 I think this is what SQLAlchemy uses to get their async await supporting. So it's certainly,
13:33 yeah, I guess popular. Yeah.
13:35 Sure. Reenter asyncio or trio event loops from synchronous code. Very nice. Cool. Okay. So
13:41 Flask patch, that's pretty good if you just have some Flask code and you're like, oh,
13:46 I want to switch to Quart, but hopefully nothing, nothing changes. And I'll just run this at the
13:51 beginning and everything will work. How often does that, how often does it work out to be
13:55 easy like that?
13:56 So it depends on what the extension does. So if the extension does its own IO or calls certain
14:02 Flask functions that we can't, this extension can't patch, then yeah, it just fails. And
14:08 yeah, so not all of them. If you go into tests, you can see an example of some of them
14:13 that we test against or I test against, sorry, just to make sure they work.
14:17 Like login potentially.
14:18 Yeah. So login is probably the most popular, I think. So I don't think it works for SQLAlchemy,
14:23 for example, there's a technical example, anywhere that uses app context as a, in a
14:29 contact manager in a synchronous location, basically that's too hard to patch. So they
14:34 tend to all fail, but yeah, a few do. And I hope it makes it easier for people. I'm pretty sure it
14:39 is used because I get bug reports. So some people find it useful at least.
14:42 Yeah, absolutely. It looks, it looks excellent. Right. So once you, once you do all the magic,
14:48 you can just work with the login manager and off it goes. Right.
14:51 Yeah, exactly. Yeah.
14:52 Super cool. This portion of talk Python to me is brought to you by Posit, the makers of Shiny,
14:58 formerly RStudio and especially Shiny for Python. Let me ask you a question. Are you building
15:04 awesome things? Of course you are. You're a developer or data scientist. That's what we do.
15:09 And you should check out Posit Connect. Posit Connect is a way for you to publish, share,
15:14 and deploy all the data products that you're building using Python. People ask me the same
15:20 question all the time. Michael, I have some cool data science project or notebook that I built.
15:25 How do I share it with my users, stakeholders, teammates? I need to learn FastAPI or flask,
15:30 or maybe Vue or react.js Hold on now. Those are cool technologies, and I'm sure you'd benefit from
15:36 them, but maybe stay focused on the data project. Let Posit Connect handle that side of things.
15:41 With Posit Connect, you can rapidly and securely deploy the things you build in Python, Streamlit,
15:46 Dash, Shiny, Bokeh, FastAPI, Flask, Quarto, Ports, Dashboards, and APIs. Posit Connect supports all
15:54 of them. And Posit Connect comes with all the bells and whistles to satisfy IT and other enterprise
15:59 requirements. Make deployment the easiest step in your workflow with Posit Connect. For a limited
16:05 time, you can try Posit Connect for free for three months by going to talkpython.fm/posit.
16:11 That's talkpython.fm/posit. The link is in your podcast player show notes.
16:17 Thank you to the team at Posit for supporting Talk Python.
16:19 CORS. Everybody loves their cross-site origin scripting restrictions. I guess we do love it
16:29 in the sense that we're not getting our bank account stolen when we browse the web. But as
16:34 a web developer, you're like, "Oh, CORS again. I need to allow this thing to talk to that." But
16:39 it can be a hassle. And so I'm guessing CORS allows you to control those settings and expose
16:47 them and so on?
16:47 Yeah, exactly that. I think it's become more prevalent since the switch to front-end
16:52 frameworks has been so popular. Because you end up serving them, typically you end up serving
16:56 them separately. But yeah, it's exactly as you described. You can put decorators on routes,
17:02 or you can put it on a blueprint or on the entire app, and you just say, "This is a CORS policy for
17:06 everything." Yeah. Very similar to Flask CORS, but different API.
17:10 So you can put it on even a blueprint and say, "This whole section of code is where the APIs
17:15 live." Things can call that, but nothing else. That's really nice.
17:18 Yeah.
17:19 Okay.
17:19 So it also has a slight web... Because obviously, CORT does web sockets whereas Flask,
17:24 at least natively, can't. But yeah, there's some web socket considerations for CORT as well,
17:29 which mostly come around to checking the origin header. But yeah, it does that as well.
17:33 Okay. So you have a route and you just put the @route_cors on it, or like you said, a blueprint,
17:39 or on the whole app, or however you want. And then what kind of arguments does it take? What can I
17:44 ask it? Make it do?
17:45 Yeah. So I went for the functional API for it. So you can put in the allow headers or... What's
17:52 the other one? The credentials. I forget what it's called now. But they're all listed and all
17:56 keyword arguments effectively to it. There was in the issues, someone, maybe last year, put out
18:01 a suggestion for a new API for CORS libraries that would be more intuitive, but wouldn't match
18:07 the actual headers that came out. So at some point, I hope to look into that and offer that as well.
18:11 But at the moment, you basically say the header and then the values you want. So it's very...
18:15 You have to really know CORS to use it.
18:17 Okay. It just basically juggles the headers and there's not a lot of abstractions in between.
18:22 No, no. So I think in the readme, it shows you them all actually. So these are all the headers you can control. And it's basically the same name.
18:29 Sure. So access control, allow origin, access control, allow credentials, expose headers,
18:35 max age, all these things that you might want to use. Right. Very cool.
18:40 Yeah.
18:40 Okay. Oh, you have types as well. So that's lovely. Are you a fan of types? Type hints?
18:45 Since it came out, I've tried not to write untyped code. I think, especially in a team,
18:50 I think it makes a huge difference to the understanding of the code. So yeah, I'm a big fan.
18:54 Yeah. I also think it makes your editors help you more. If it knows what type that is and you say
18:59 dot, it'll... Is this what you meant? Like, yes, actually, I didn't know that existed,
19:03 but that's what I want. Give that to me. Without types, you're kind of like, well,
19:06 you could object or weird, useless stuff that you can't do anything with.
19:11 All right. So CORS, if you need to do CORS, there's, I guess, let me know how to do this,
19:16 but I think pretty much most of these that exist, there's probably a Flask variant. So if you use
19:21 Flask and you're like, oh, I need CORS, but I don't use CORT, just Flask-CORS instead of
19:26 CORT-CORS. Yeah. Yeah. I think it's quite well maintained, the Flask one. So yeah,
19:30 the Flask CORS, it's slightly different API, but the concepts are all the same, obviously. So yeah.
19:34 Another one is, auth means so many different things. Is it a session type-based auth? Is it
19:42 OAuth? Is it, who knows what, YubiKeys? What are we talking here?
19:46 Yeah. Session management via a secure cookie. So it's a similar usage if you're familiar with
19:52 Flask login, but I didn't want to call it CORT login because it didn't really do anything with
19:56 the login. It didn't check the password or the username or an MFA or anything to do with login,
20:00 in my opinion. So I called it auth instead. But yeah. So with this extension, you tell it to log
20:06 in the user and give it some kind of identifier and it saves that information securely in a cookie
20:12 for you. And whenever the user comes back and presents the cookie, it then pulls it out and
20:16 turns it into what is called the current user. So I think it's called the current user in Flask
20:21 login as well. So very similar to that. All right. So you get this auth user back.
20:24 What is this auth user? It's not like out of your database, right? It's some, it has properties.
20:29 By default, it's just as a method to say whether the user is authenticated and to return their ID.
20:36 But if you customize it, if you extend it, so what I typically do in my code base is I extend it and
20:41 I add methods to return a model from the database, for example. Yeah. Very cool. Nice. And you know,
20:48 there's a lot of fancy login auth stuff, but nice session cookie. You know, it just,
20:54 this works these days. It still works really well. Does yeah. Much simpler than JWTs and
20:59 less likely to go wrong, I think. It's not exactly related, but I've had a lot of websites now I've
21:06 given up on passwords, but they don't use something new or they just decided like every time you want
21:11 to get to the site, you just, we're going to email you a code and you enter it. When did that become
21:16 the way we're going to log into everything? It's driving me crazy. So some good old username,
21:21 password, we're going to store your ID in a cookie. It warms, warms the heart.
21:24 But I've been playing with the web auth in little USB keys and such recently. I quite like it. I
21:31 think it might be a bit complex still. It's a little tricky to get working, but yeah, I think
21:37 that's probably the future to be honest with you. The site just sees like, yeah, you got the right
21:40 hardware thing. Off we go. Peter knows you're there. Something like that. Right. Nice. Okay.
21:44 So people need to deal with users. This is not about storing them in the database, encrypting,
21:51 hashing their passwords or those types of algorithms. It's really just about the web
21:56 browser exchange that involves setting a cookie when you're logged in and getting it back and
22:00 make sure it's not tampered with. Right. Yeah. Yeah. So what it does is it stores the,
22:04 the value, like the, here, the number two is the ID for the user. So it stores that in the cookie
22:11 in plain text, but then cryptographically signs it. So you don't want to store anything
22:15 sensitive in the cookie if you're already about to be in red, but you can store,
22:20 identifies, et cetera, and know that it hasn't been tampered with.
22:22 Yeah. That's the thing. It's not going to harm you to know that your user ID is two or
22:27 seven, five, one, one, seven or whatever. But if it's two was to stop somebody from going,
22:33 let me change that to a three and see what happens. Oh, look, I'm an admin. How cool is that?
22:39 Right. That's the problem that you're trying to solve with secure, right?
22:42 Exactly. Yeah.
22:43 From my side, I bake that thing in myself, but having an extension that just does it,
22:47 that sounds better.
22:48 Yeah. So the nice thing, well, the bit I took a bit of interest, I think it's changed now
22:52 with Flask login, but a couple of the areas where I wanted to improve is it does same site
22:58 settings on the cookie by default. So it will start with the strict, and then you can play
23:02 around and go down to lax if you so need. But it also has the special, it allows the
23:07 special host, the double underscore host prefixes on the name and stuff like that.
23:12 Oh, right. Yeah.
23:13 Although I don't think in reality that adds that much to your security, but it's a safety net
23:18 in some respects.
23:19 Yeah. Sometimes, well, also it becomes a hassle when you're doing development because your host
23:24 is localhost and you're saying it has to be on mysite.com. You're like, well, I can't log in
23:28 anymore. So then you've got to have localhost. And then I don't know, it doesn't feel super
23:32 tamper resistant to me either. Are you familiar with securepy? There we go. This is an interesting
23:39 project too.
23:39 Oh, it's been archived.
23:40 Oh, it's archived. Oh, sadly. Is there a new one? No, but I think it still works. I'm not
23:45 sure what, been deprecated. Okay. Well, I guess that's not being used as well, used that much,
23:51 but it used to be a way to kind of set all those different things, I believe. Or is this different?
23:55 This is a totally different.
23:56 No, I think this is, I thought you were going to say secure headers. I think, is it called that?
24:00 Oh, I think it's just called secure. What it is. Yeah. That's the one. So sorry. That was some other
24:06 random thing that we've done that was called securepy, but not, this thing is still alive
24:10 and supports court, which is why I brought it up amongst like lots of other frameworks. But yeah,
24:15 you were going to tell people about this. This is cool.
24:17 Yeah. So I pretty much ended up adding all of these by hand. So yeah, you, most of the headers
24:24 you want to add, and if you ever do a pen test, right, they're going to, if you don't have these
24:28 headers by default, there's going to be a finding straight away. So yeah, it just adds to all the
24:32 ones you want really.
24:33 Right. For one of the ones that you might not think of that you should probably have,
24:37 unless you have, unless you have a really specific use case is what if somebody created a site,
24:42 you know, you had service.com and they had service.io or, you know, whatever, and they,
24:48 they bought that and then they could put your, whatever their URL is, your, it just into your
24:54 site as a hundred percent width and height iframe. Right. And then they just capture all the key
24:59 strokes. Like, huh, you're typing into your thing. It looks like your thing, your data is there.
25:04 You don't want people to do that to your site or just randomly host your site and their site. So
25:08 for example, this secure thing will set X frame options to same origin. So you can't embed.
25:14 Your site can no longer be embedded by default. You've got to choose to let it be embedded. All
25:17 those kinds of things. Right. Are really nice here.
25:20 I actually got caught out by that many, many years ago. I built a, an image photo editor
25:26 in the browser. It started to get quite a lot of use. I was like, Oh, this is great. And then I
25:30 realized it was on somebody else's domain, basically in an iframe.
25:33 So like, look at our amazing editor that we're getting Google AdWord money for or whatever.
25:38 Right. Yeah, exactly.
25:40 Yeah. So a little bit of a header action will shut that stuff down. But I mean, I didn't know to do
25:45 it until I kind of dug into it more, right. Or you find out your site somewhere else.
25:49 I think it's like a lot of it seems to be learned with experience, I guess, isn't it?
25:55 Or on the job.
25:55 It only happens once when you see your web app in somebody else's site embedded as an iframe. You're like, no, this is not going to be okay.
26:02 Awesome. All right. So that was court auth as well as the secure thing.
26:07 Next, I guess, kind of related to security, maybe not exclusively, but someone rate limiting.
26:14 Yeah, because I mean, on a login route, you'd want to put a rate limiter on
26:17 straightaway to stop any kind of brute forcing anyway, right?
26:19 So, oh, absolutely. And the ability to do really nice rate limiting in ways that punish the rate limit breaker is so much easier on async things like court,
26:31 right? Because if you don't want to be overwhelmed by them, a lot of times you just say you send them
26:36 up some kind of response code item with the status code is like too many requests or something like
26:40 some 400.
26:41 429.
26:42 There you go. So you can just send them back straightaway, but there's no cost for them
26:47 to receive that 429. You know, they're just like super quick going. If you want to say,
26:52 like, because maybe they're trying to guess passwords or something, you're like, let's not
26:55 let them guess so quickly. So you can like sleep and then respond to them in 10 seconds later.
27:02 So it's like, oh, well, I don't know if the password good or not. Why don't you wait around
27:06 for a while instead of going somewhere else with a asyncio based website? That's a weight async
27:10 IO.sleep, nearly zero cost to you other maybe an open socket. And you can just like send them to
27:16 sort of time black holes, super easy. Whereas like a WSGI app, you're blocking a request and
27:21 you can only handle so many and all that kind of stuff. Right?
27:24 Yeah. So this one sadly doesn't do any of that fun.
27:27 It doesn't have punishment built into it. No, but tell us what, how it works and what it does.
27:32 I was going to say, I think there was an article today suggesting you should serve
27:35 zip bombs or something to like unpleasant requesters, but yeah, it doesn't do any of that.
27:40 I was going to send them like a hundred gigs back or something.
27:43 Yeah, zip bombs would be good. That would be good actually.
27:46 Yeah. So this one implements rate limiting as it's defined, like a user would be allowed to
27:51 do so many requests in a certain time and also implements the RFC for the like headers that
27:58 you're supposed to send back when they over, when they go over the rate limits. So they know when
28:02 they can, or when the client can then request again.
28:04 I see. Like you've made too many, rather than just saying too many requests,
28:08 try again later. You're like, and you'll be reset in five minutes or like there's a proper way to communicate that.
28:13 The thing I find really interesting about this, which I don't know if everyone does,
28:17 but I'll say anyway, but most people implement rate limits in like a leaky bucket or something
28:22 like that. And these are the standard algorithms. And typically that requires you to store two
28:27 variables per key per rate limit you're looking at. But there's a, I think it's not a very well
28:32 known algorithm called the generic cell rate algorithm. I think that's right. And this allows
28:38 you to store one algorithm at one variable per rate limit. And it's really quite nice.
28:43 The algorithm is very, when you, when you get it, it's really intuitive and clean. You basically,
28:48 what you store is when you expect the next request to arrive. And if the request arrives
28:52 too early, then you just rate limit it. And yeah, it's really clever. I think so. I'd like more
28:57 people to know about it basically, because it's a really nice solution.
29:00 It's a great solution. Yeah. So this, it doesn't slow them down. It just tells them that they've
29:05 gone over the limit in this particular one. Right. It's not like if you expect it to be in five
29:10 seconds, you don't just sleep for four and then let it in.
29:13 No, no. So the idea is that this decorator is much less costly in terms of your compute
29:18 than your function, your handler. So this decorator will just send back a four to nine
29:23 very quickly and never touch your route instead.
29:25 Okay. Interesting. I'm starting to think about maybe if you have async and await
29:30 available and your algorithm told you when the next one was coming, you could just
29:34 sleep them until the next one's allowed potentially if it wasn't 10 minutes.
29:38 Right. Potentially. The client, I mean, it depends on your rate limits, but there's a
29:42 good chance the client would timeout. Right. So.
29:44 Right. It'd have to be like five seconds or something real short. Right. Like I don't
29:48 want more than one request a second type of rate limiting, not like five per hour,
29:53 then they would definitely timeout. But yeah, there's a lot of a lot of things you can do
29:55 with this. So basically the way it works for people listening is you can set it up on the
30:01 app. So the entire app itself, all the requests to it are limited. Right. Or you can go and
30:07 put kind of like tenacity. You put the decorator exactly on a particular endpoint and then
30:13 set the details for that one. Right.
30:14 Yep. So a bit like cause you can do it per app, per blueprint or per route, depending
30:19 on what you need.
30:20 Okay. And here you say rate limit one and then a time Delta. What's the one?
30:23 It's the count. So you might want. So for a rate of one per second, you might want to
30:28 express that as 10 per 10 seconds. So it would be instead of the one, the 10. And that allows
30:33 you a bit of bursting if you will. So, you know, that 10 can come in one second or they
30:37 can be spread over. So yeah, it allows you to express the rate limits in different ways.
30:42 Really. Yeah. I've seen this probably bypasses the static files. That true?
30:47 If you do the default limits, I think that applies to static files as well. I can't remember
30:52 now, but I think that's what it does.
30:53 You might have, you know, six CSS files, 20 images and so on. Right. And like one page
30:58 request might blast those all back super quick.
31:01 Yeah. Yeah. You certainly want a higher limit.
31:03 Yeah. But presumably you're using something like Nginx or whatever, or maybe even a CDN.
31:08 And then all those other things I described don't actually go through the Python app.
31:13 Ideally.
31:14 Yeah. Yeah. I mean, it does work if you do it for the Python app as well. So up to you,
31:18 I think. Yeah.
31:18 Indeed. All right. WTForms. Next up, QuartWTF. What is this?
31:24 So this was, I'm tempted to say it's been a bit less popular recently with the front
31:30 end frameworks and stuff. But when you did template rendering, you'd render the form,
31:34 then you do the validation in your Python framework for that form. And you pass back
31:38 the errors. And basically it would, the WTF framework would do a lot of that for you.
31:44 So you could just put in like in your template, an input box, and it would do the errors and do
31:49 the, like you can see here, you could do the CSRF tokens and render all the HTML for the
31:56 labels and the actual input boxes and get it all hooked up very easily. And that's what it does.
32:00 Yeah. It does all that. And the validation, when it comes to the backend as well, I think
32:04 you can just do formed up, validate on submit or something like that. Yeah, there we are.
32:08 And that's it. That's your whole validation. So it saves a lot of effort, really.
32:12 Yeah. That's pretty handy. And I'm guessing you're getting generally generic HTML out on the client side so you can CSS style it up while you like.
32:21 Yeah. I think it's, I can't remember exactly. I think WTF might have hooks so you can
32:26 put styling directly in, but yeah, it's pretty, yeah, pretty generic as I remember it.
32:31 Yeah. Some frameworks require explicit stuff on say the input box, like form dash control would
32:37 be bootstrap and things like that. Right. There's probably some way to set a class.
32:41 Yeah. I think there's a class name argument. I'm trying to, I may have misremembered that.
32:46 I think it was called class name.
32:47 Yeah. Sure. Nice. Yeah. I haven't used this really, but I've used it kind of like it in
32:52 framework, other frameworks. In the end, I ended up just going back to writing my own
32:56 HTML because I felt like I was getting 60% help, but maybe 30% struggle to fight the system. You
33:04 know what I mean? And then like, all right, you know what? I just know this stuff well enough.
33:07 I'm just going to do it myself. And, and, but if you don't want to mess with it, you want to some
33:11 sort of quick development type of thing, or maybe you're not super familiar with HTML, this will
33:15 definitely help. Right.
33:16 Absolutely. Yeah. I think you can also write your own HTML instead and just let it do the
33:20 Python validation part.
33:21 All right. As long as it lines up. Yeah, sure.
33:23 Yeah. I think it's just the name that matters. Like your input has to have the right name.
33:27 Yeah. Otherwise how would it know? Right. It couldn't, it couldn't know.
33:30 Yeah, exactly. The CSRF is quite easy and nice. That just puts a hidden input in with the token
33:35 in and it's all taken care of for you. So that can be quite nice.
33:38 Yeah. Excellent. All right. Schema.
33:41 So schema.
33:42 For APIs, right?
33:43 Yeah. So it's possibly the kind of more, I'll say modern, but I'm not sure if that's the word I
33:47 mean, but yeah. So as an industry, we seem to have moved away from the forms of old and form
33:53 data to send in JSON all the time. Right. And a court schema is about saying, I expect this
33:59 structure to arrive, validate it for me. So it's a very similar, it's very similar to FastAPI,
34:04 which I'm sure a lot of people know.
34:06 Okay. That's pretty interesting. So like you say, very much like FastAPI, it kind of brings in
34:12 some frameworks called model binding, where you can say there's an argument and it's of this type.
34:17 And then, you know, like a class, a data class or something along those lines, and it'll, here,
34:22 it's a data class and it'll take all the inputs, right? Query strings or route primers, whatever,
34:28 and parse them and map them in there. And then just give you an object and go here, it's ready.
34:32 Yeah. So for this library, it supports, I've used all the documentation as data class, but it's
34:37 Pydantic based at the moment. And because of the, it's not actually the typing. So there's
34:41 no dependency injection or anything like that with FastAPI, but it uses the decorator validate
34:45 request and you pass it the model. And if the request that arrives can be turned into that
34:51 structure and validated as such, then your function will be called and that'd be passed in as data.
34:57 If it can't, then the client will get a 400 response.
34:59 Bad requests, bad data.
35:00 Exactly. Yeah.
35:01 Okay. What's the, so validate requests makes sense to me. It means like, here's your
35:06 structured object that comes in, passed in as data. What's validate response?
35:11 So it's the same concept, but going back. So if you return like a to do class instance itself,
35:18 then there's no validation to do, right? You've already said it's the right structure,
35:21 but often you'd return a dictionary or something like that, right? And expect it to go back as Jason. What this will do is it will say, I'll check that dictionary
35:30 is of the right structure. And if it isn't, it throws a 500 response to the client and an error
35:36 that you can go and view in whatever error monitoring tool you've got.
35:39 Sure. Okay. That's excellent. So I see you have data classes here and you said it also
35:43 works with the pydantic.
35:44 Yeah. So, well, actually a shout out to one of your previous recent streams is I'm working on
35:48 msgspec support at the moment. Cause I actually quite like that. So.
35:52 Message spec is interesting.
35:53 So it will support pydantic and msgspec. So you'd be able to use pydantic data classes
35:58 at as msgspec stuff. Well, the msgspec struct, I think that's probably all of it,
36:03 but yeah, quite a bit of choice.
36:04 The struct classes really, they've done some super interesting things here to make these
36:10 data classes almost better for performance than even regular, and for memory and stuff
36:14 than regular Python classes. It's quite impressive.
36:16 Yes, absolutely. Yeah.
36:18 Yeah. For people who don't know, maybe just tell them real quick what msgspec is.
36:22 They probably know data classes and pydantic.
36:24 Yeah. So much like data classes, and you can see here, it's a nice, simpler way of creating a class with various things created for you.
36:32 But it also, much like pydantic will do validation when you try and if you use one of its functions
36:38 to convert raw data, like a dictionary into this type and much like pydantic as well,
36:43 it also gives the ability to take this model and turn it into a open API, JSON schema definition.
36:50 And that's the final thing that Quartz schema does is, once you've decorated all your routes,
36:55 it will auto generate a open API definition for you as well.
36:59 Oh, it will. Okay. Yeah. Using flaggers/docs. I think that's really nice. A lot of APIs just
37:05 don't, they're like, "We're not going to do that." If they don't have something automated to do it
37:09 and having typed classes representing all your inbound and outbound data, pretty easy, right?
37:14 Yeah. I mean, it really helps with mypy, right? Because instead of having like your request.data
37:19 or something like that, which is some dict that as far as mypy concerns can be anything,
37:24 it must be a to-do instance in this function now. So yeah, really helps with your type checking.
37:29 Yeah, absolutely. All right. What do we got next? Sock from Miguel Grimberg, Flask websockets.
37:36 This one, there is an extension for, because this is one of the areas that Quartz being
37:39 natively async can just do itself. So the Quartzer, I think I described sometimes as a
37:44 superset of Flask. So you can do everything Flask can do. And a bit more.
37:47 The C++ of Flask.
37:49 Yeah, exactly.
37:51 Awesome. So websockets, a problem that websites generally have not been able to do. Once you
37:58 make a request, usually there's the response and that's it. But what if the server, you want to
38:02 send more stuff to the server, everyone wants to send stuff out to you later. Like I started this
38:06 job three seconds later, now it's done. Or this bidirectional exchange is really cool. But a lot
38:12 of times I feel like it's a little over the top. Like a lot of times I think people maybe just want
38:17 push notifications from the server and not true. Like let's have a conversation. Like I just need
38:23 to know when this happens. Just let me know as soon as it's done. Right. So for that, yeah, go ahead.
38:27 Server sent events, sorry, seem to be not that well known, but yeah, probably described your
38:32 usage really well.
38:33 That's exactly what I was getting at. So does Quartz support SSE? Does it support
38:37 server sent events?
38:38 Yeah. Yeah. I think there's an extension for it, but in reality it's a very small bit of code you
38:44 need. So it, yeah, I mean, cause you literally return a response with a certain header and then
38:51 you just return a, well, here we go. Here's the, there's an example in the documentation for
38:56 Quartz, but you just return a certain data structure and then you've got it. Yeah.
39:00 Yeah. So I think that's when people say they want web sockets, I think most of the time this is what
39:05 they want. They want the client, the server to call the client sometimes, not just the other way
39:09 around.
39:09 Yeah. So I'm going to go slightly off topic cause it's interesting, but it's related to this, but
39:15 one of the things I implemented in the Hypercorn cause I find it really interesting is I think
39:19 two, no, it must be more like four years ago, they introduced web sockets for HTTP/2. And of
39:24 course, one of the reasons you want web sockets, because the cost of opening the connection for
39:28 HTTP/1 is fairly expensive. So you can keep it open and do the bi-directional, but with HTTP/2
39:34 you don't even need an extra connection anymore. You can have your web socket stream on the same
39:38 connection as all your requests as well. So it's really nice and efficient. So yeah, I'd love to
39:42 have an opportunity to make more use of it cause I find it really interesting, but the code I write
39:47 professionally at the moment doesn't quite need that level of real time interaction.
39:50 I know, I know all this stuff I built, I'm like, that would be so fun, but you know,
39:53 I just don't need that. I have no use case. I mean, there are apps I'm sure that do it right.
40:00 Like really complicated single page apps like StreamYard that we're using right now, for
40:05 example, this has probably got some crazy interchange going on, but I don't write this
40:09 kind of apps either. All right. Let's see. So the socket stuff, which is cool. That's,
40:16 like you said, built into Court. That's one of the advantages of it, but you know,
40:19 you can use Miguel Grimberg's stuff to add it to Flask. SQLAlchemy, you mentioned them before,
40:25 probably outside, if you're not in the Django space, it's not the only option for sure,
40:30 but it's probably the de facto choice people, when they think I want an ORM for a relational
40:35 database, you probably start here, right? Yeah, I think you would. Yeah. And this is a port,
40:41 as I understand it, of the Flask SQLAlchemy extension to work with, or natively work with
40:47 Court. So should match all your expectations, I think. And if you're used to it, you just
40:53 use this and it works with Court, and there you go. Let's see an example here. You can set up
40:58 your database with a pretty interesting nested type of construct here, like just, you know,
41:05 passing rich arguments that take more. And if you look at the code on there, just the shape of the
41:10 code is kind of unique here, but you end up with this constructed database thing, and then it has
41:16 the base model class and off you go, right? You just create a session from it, do all the things.
41:22 I've always had mixed feelings about these things that you set up here that are built, like,
41:26 into the web frameworks, essentially, that make them part of the request,
41:29 because if you're going to go and write little scripts that also do data stuff, you want to
41:34 be able to get access to the data and the models over there as well. Although this looks like it
41:38 does take the app there, but I don't know if it really needs a meaningful app. Like, I don't
41:43 think that it would use it, probably, but it just adds it to the request. No, I don't think you need
41:47 to be within an app context to use the DB. I may be wrong there, but yeah, I think you could probably
41:52 use it in a script. Yeah. >> Yeah, that's the only thing that concerns me about these, like,
41:56 database helpers is, like, you know, I need to do database stuff outside of a request as well.
42:01 Even potentially some of the stuff that we're going to talk about in a minute, like, it's just
42:04 a background task that's not part of an actual request, but it could even still be in the web
42:09 server process, right? >> I use, there's an extension called Cort DB, which is my preference,
42:13 because I prefer to write the SQL and go down the ORM route. And yeah, that works as you'd expect.
42:21 You'd still set it up and pass it the app, which I think is true of this, but then you can just use
42:26 it without an app context. It just basically uses the app to get the configuration values,
42:31 so it knows where the database is. >> Interesting. And now we're on to probably the biggest one that
42:36 you want to talk about, which is quite exciting, Fort Tasks. >> Yeah, so this is a very recent
42:42 release that I haven't announced anywhere else yet. This is something, I think I read a post
42:48 recently that said, for your web framework, you need, like, five things. Like, you need
42:53 a database connection, you need some kind of validation, input/output, rate limiting,
42:57 authentication, and you need Array to run periodic tasks. And in the past, my way to do this was
43:04 extra infrastructure. So it was Cron running somewhere, or some kind of, I think CloudWatch
43:10 does some kind of event generator based on a Cron schedule. And for me, that's just annoying,
43:16 because now I have to build extra infrastructure, which I don't need. I'm not at that scale.
43:20 >> Yeah. Maybe you've got Celery or RabbitMQ or something like that, right?
43:23 >> Yeah, exactly. But again, with those, you need that extra infrastructure for it to work.
43:27 >> Yeah. It's another server. It's another thing that could get hacked. You've got to patch it.
43:31 You've got to secure it. You've got to know how to run it. It could go down, and then
43:34 none of your background stuff works anymore. It's all a hassle, even though it has a benefit.
43:39 >> Yeah. So what I really want to do is make use of the async event loop and just run a task in
43:45 the background periodically. And obviously, at some point, this will normally only scale,
43:49 because I have so many background tasks that dominate the server and stop it doing requests.
43:53 But while I'm small and I'm starting out, this just makes life so much easier.
43:56 >> Absolutely. It totally does. And because everything's async and await,
44:00 you can just set up the task and just await the task. And it just blends in,
44:05 hopefully, well with all the web requests, right?
44:07 >> Yeah. Yeah. So for core tasks, if you write your task as async def, then it will run it in the event loop. If you write it as a def, it will run it on a thread.
44:17 So if you have synchronous code that's going to block the event loop, like you're using
44:21 traditional I/O for want of a better phrase, then if you just use def, it will run it on a task,
44:26 won't block your event loop, and you'll be fine. So yes, it's nice in that respect.
44:30 >> Yeah. And as long as your async pieces are fine-grained enough, you probably won't know.
44:37 >> Yeah.
44:37 >> Yeah. It's just if you run a whole bunch of synchronous code or something like that,
44:41 you might start to clog things up, but don't do that. Honestly, what I find anyway on the web
44:48 most of the time is you're talking to other things, talking to a database, talking to a cache,
44:52 talking to an API. All those things are perfectly suited for await, because most of the time,
44:59 you're almost outside the web app.
45:00 >> Yeah. I think the perfect one for this that I keep thinking of is when you want to send a
45:05 periodic email, like a reminder or a summary of the day, just write this decorator task run,
45:11 and you have your function that just sends emails. Yeah, that's why it exists for me.
45:15 It makes that so much easier.
45:16 >> Yeah, absolutely. Right. Yeah. Like you say, just send out a quick email, like, "Hey,
45:20 I need to reset my password." All right, here, we're going to send you call an API,
45:23 like SendGrid or something like that. We're going to send you, but you don't want to block
45:27 things up. You just kick that onto the background work and off it goes.
45:31 One of the things that people would say, "All right, you guys, I know you're excited about
45:36 async, but we need durability. What about, what if the server goes down? Or what if another
45:43 potential issue is what if you scale, like at least the cron stuff? Like what if you
45:48 ban out your web app into like 10 worker processes?" And you say, "Run in five seconds."
45:53 And they all run in five seconds. TEN times or those kinds of issues here.
45:59 >> So this extension supports a couple of extensions to it. So you can pass it a custom
46:06 store, which allows you to store when the task last ran. And so that means if the server
46:11 goes down and it misses an invocation, then when it comes back up, it will recognize that
46:15 and run the task straight away rather than wait till the next time. And the other thing
46:20 it supports is you can decide what should happen before the task starts and what should
46:25 happen after. So this allows you to put some locking in for your second case. In both cases,
46:31 the reason it's not built in to core tasks is you'll need to decide where you store this
46:34 information, where do you store the lock? Where do you store the... So usually a database
46:38 works for that. So yeah, so you can add it all, but you've got to choose how yourself.
46:42 >> That's cool that it has placeholders and ways to plug that in. That's what I was thinking
46:47 I was right. Like instead of just storing it in memory and throwing it on the queue,
46:50 put it into the database and have the task just check the database and go, okay, I'm
46:55 working on this one. No one else do this. We don't want to send 10 emails to the person.
46:59 I got it. Right. All that kind of stuff. >> It's not a job queue, although I've been
47:03 thinking about that recently. So yeah, it can't be like, it doesn't work in the sense
47:07 that job will run once, it will do jobs periodically. >> Got it. Okay. So more of a scheduler
47:13 type of thing. So I need to see if anyone's uploaded files to this location and then convert
47:19 it from PNG to WebP or like safe space or... >> You could be like, every morning I want
47:24 to send out an email to the users and say, imagine it's a to-do app because everyone
47:28 uses that as an example and say, these are your tasks for the day or something like that.
47:31 >> Sure. People who've signed up for the daily digest, send them their digest, right?
47:37 >> Yeah. >> Yeah. Very cool. And because it's on a background thread, it's not going to harm anything. Excellent. Okay. Well, it
47:43 looks pretty simple again. Very decorator driven, right? >> Yeah. So I mean, with all
47:48 of these, the extensions that they start for it and I've tried to follow the kind of flask
47:52 conventions for better, for worse. So yeah. Same with Quart, it follows Flask. >> Yeah.
47:56 It feels like if you're already doing Flask or Quart, you just know what to do. It's just
48:00 similar. >> Yeah. >> Excellent. I think we've covered all the ones we explicitly said.
48:06 Let's make sure we get a chance to cover those. I definitely want to encourage people to check
48:09 out the Quart extensions page because there's quite a few more. I'll see if maybe some jump
48:15 out at me here that are kind of cool. Quart B. >> We briefly mentioned Quart DB, which is one
48:20 I maintain. So I could say a bit more about that if you're so interested. >> Yeah, sure. And it's
48:24 hanging out right here on your list. >> So this is much like the SQLAlchemy one. It's a way to
48:30 connect to a database, but unlike the SQLAlchemy way, it doesn't promote an ORM. Rather, it just
48:36 gives you a connection that you can run SQL on. I don't know you can do that with SQLAlchemy. I
48:41 just, I mean, typically that's not what people do. So in this case, like the example here is you
48:46 pass it the URL you want to connect to and it supports Postgres and SQLite at the moment.
48:52 And then in the roots, you have this G.connection object that you can make use of. And you can make
48:57 use of that in background tasks. And it's quite easy to make use of it actually in Quart tasks
49:02 as well. It's just a little snippet you need to do. But yeah, and that's it. You get a connection
49:06 that you can do whatever you want with. >> Yeah, okay. Very nice way to just have everything set
49:11 up right. Like you don't have to worry about passing things around and create a transaction,
49:15 go do your things, right? Excellent. We got Quart events for broadcasting server sent events and
49:21 WebSockets. That's pretty cool. >> There is one, yeah. >> Yeah. Another one that's cool is
49:25 minifying all of your elements, right? CSS JavaScript. >> I've been doing that in the build
49:30 stage a lot recently. There's a little snippet I can share if people want for Quart where you
49:35 change the static function. So it will serve a gzipped version of the static file over the
49:42 un-gzipped one if the client will accept it. And yeah, then if you do it build time,
49:46 because I think this one, and it might be a mistake, but I suspect it works at runtime.
49:50 So it compresses at runtime. >> It sounds like it does, yeah. It talks about doing it as a response,
49:55 right? So yeah. Oh, it does have a cache though. >> Ah, okay. Yeah. Where you get similar, yeah,
50:00 very similar to what I'm suggesting there. >> Excellent. Now those are really, really handy.
50:03 If people go and they want to make their website better, check out, I guess, PageSpeed.
50:09 >> Or the Lighthouse. >> Lighthouse is what I was going to call it. Yeah, but they renamed it
50:13 to PageSpeed. So you can go put in, and talk Python and see how we're doing. But it'll give
50:18 you things like how is your site fast enough? Let's see how the desktop one's doing, right?
50:23 It'll give you a score and are we going to index you well, or are you going to be punished for
50:29 having some kind of crappy experience? And this minification stuff that we're talking about can
50:33 help there, right? >> Absolutely, yeah. >> Similar to your DB, your QuarkDB, we have QuarkMotor,
50:39 which is the async database access library for MongoDB. So I suspect it's super similar. I
50:45 haven't used it because I use Beanie, which is like ORM style. So kind of on top of those things.
50:50 Keycloak is interesting, or Redis also is pretty interesting. Uploads, let's finish it out with
50:56 uploads. We'll call it after that. So I haven't used this, but uploading files can always be a
51:01 bit of a hassle, right? >> It looks very similar to the Flask uploads one. So it's kind of a nice
51:06 wrapper around how you get access to the files you've uploaded and what you do with them kind
51:11 of thing, yeah. >> Yeah, that can be a little bit of a hassle, right? You got your request.files
51:16 and all that. Don't know how to use this thing, but I know that people kind of struggle uploading
51:21 files a lot, so that can be a thing. >> I don't tend to actually write it that much come to think
51:26 of it, but yeah, it just makes it easier. >> Yeah, exactly. Profile pictures, maybe,
51:32 is like that kind of stuff I'd be doing. Anyway, I guess I've wrote some code that lets you upload
51:37 MP3s at one point for podcasts, but I stopped using it. So it's not top of mind anymore. Anyway,
51:44 excellent stuff here. I love just going through and seeing all these little pieces, these little
51:48 building blocks you can bring together and just quickly build an app. So thanks for being on the
51:52 show and telling everyone about it. >> Oh, thank you. Thank you, yeah. Great, hopefully, opportunity
51:57 to introduce some stuff that I hope people will find useful. >> Yeah, and just more broadly,
52:01 like thanks for working on Quart and pushing that forward. >> Yeah, I mean, like I said, our aim is
52:06 hopefully we'll, well, we're kind of going around in different ways of how we actually might merge
52:11 Flask and Quart, but that's still the ultimate aim. I kind of think because of the limitations
52:16 we were talking about earlier about running async code when you're deep in async code base,
52:21 I think you'll always have to make a choice with Flask where if you're going to be mostly sync,
52:25 you go Flask. If you're mostly async, you go an async Flask or whatever it's called,
52:29 which at the moment it's called Quart. We might be able to put them in the same code base perhaps,
52:33 so then you can maybe mix and match because you can certainly run Flask and Quart together with
52:39 Hypercorn as the server and just have a, what's called a dispatch middleware layer in between
52:44 that says for these routes, it goes to Flask and for those routes, it goes to Quart. So yeah,
52:48 there are lots of ways to do it, but it depends what you want to do really. >> Yeah, that's
52:52 interesting. Maybe you could even create a different app, sort of app equals Flask,
52:56 it's app equals async Flask, who knows. >> That's effectively what Quart is. I mean,
53:01 I could have called it async Flask at the start. >> Yeah, you're right. A Flask, yeah, ranks higher
53:07 in the alphabetical sorting. All right, so people are excited with Quart. Maybe it's back on their
53:12 radar now that we've been talking about all these extensions. What do you tell them? How do they get
53:16 started? What do they do? >> I mean, you can just pip install Quart and a quick start is as simple
53:22 and familiar as it's ever been for Flask. You just do app equals Quart and then app.get or app.whatever
53:28 and then your function below to handle it. >> Yeah, the selling point is basically,
53:32 if you know Flask, you know Quart. You just use the word async some of the time. >> Yeah, yeah,
53:36 yeah. I mean, even the WebSocket support, I've tried to make it as familiar as it would be if
53:41 you had new Flask. So instead of using the global request, you now use the global WebSocket. So
53:46 there's WebSocket.headers and you can do WebSocket.send, send a message, that kind of stuff.
53:50 So hopefully it just feels familiar. >> Yeah, fantastic. I'm sure it does. And
53:55 thanks for being on the show and nice to have you back. >> Thank you. >> See you next time.
53:58 >> Yeah, you bet. This has been another episode of Talk Python to Me. Thank you to our sponsors.
54:04 Be sure to check out what they're offering. It really helps support the show.
54:07 This episode is sponsored by Posit Connect from the makers of Shiny. Publish, share,
54:12 and deploy all of your data projects that you're creating using Python. Streamlit, Dash, Shiny,
54:18 Bokeh, FastAPI, Flask, Quarto, Reports, Dashboards, and APIs. Posit Connect supports all of them.
54:25 Try Posit Connect for free by going to talkpython.fm/posit, P-O-S-I-T. Want to level up your
54:32 Python? We have one of the largest catalogs of Python video courses over at Talk Python.
54:36 Our content ranges from true beginners to deeply advanced topics like memory and async. And best
54:42 of all, there's not a subscription in sight. Check it out for yourself at training.talkpython.fm.
54:47 Be sure to subscribe to the show, open your favorite podcast app, and search for Python.
54:52 We should be right at the top. You can also find the iTunes feed at /itunes, the Google Play feed
54:57 at /play, and the Direct RSS feed at /rss on talkpython.fm. We're live streaming most of our
55:04 recordings these days. If you want to be part of the show and have your comments featured on the
55:08 air, be sure to subscribe to our YouTube channel at talkpython.fm/youtube. This is your host,
55:15 Michael Kennedy. Thanks so much for listening. I really appreciate it. Now get out there and
55:19 write some Python code. [Music]
55:40 [ required co presence in the comment section below ]