WEBVTT
00:00:00.020 --> 00:00:01.500
How many cores does your machine have?
00:00:02.080 --> 00:00:02.220
10?
00:00:02.720 --> 00:00:02.960
18?
00:00:03.740 --> 00:00:06.840
Well, your async Python code uses just one of them.
00:00:07.580 --> 00:00:09.060
That isn't a bug in asyncio.
00:00:09.420 --> 00:00:10.180
That's the design.
00:00:10.840 --> 00:00:16.940
And things like optimizing the event loops to be faster by maybe 20% with uv doesn't fundamentally
00:00:17.090 --> 00:00:17.480
change that.
00:00:17.760 --> 00:00:20.520
That's why Giovanni Bariliari started over.
00:00:21.180 --> 00:00:26.400
Joe is the creator of Granian, the Rust-based server that powers Talk Python and all the
00:00:26.450 --> 00:00:27.740
other things we run here.
00:00:28.020 --> 00:00:33.580
His new project is Tone.io, an async runtime written from scratch specifically for free-threaded Python.
00:00:34.280 --> 00:00:38.320
Real threads, a handful of primitives instead of async.io's pile of them,
00:00:38.820 --> 00:00:43.260
and it flat out refuses to even start if the GIL is present.
00:00:43.880 --> 00:00:49.600
This is Talk Python To Me, episode 561, recorded September 1st, 2026.
00:01:10.620 --> 00:01:14.940
Welcome to Talk Python To Me, the number one Python podcast for developers and data scientists.
00:01:15.460 --> 00:01:16.840
This is your host, Michael Kennedy.
00:01:17.220 --> 00:01:20.760
I'm a PSF fellow who's been coding for over 25 years.
00:01:21.400 --> 00:01:22.520
Let's connect on social media.
00:01:22.840 --> 00:01:26.000
You'll find me and Talk Python on Mastodon, BlueSky, and X.
00:01:26.190 --> 00:01:28.120
The social links are all in your show notes.
00:01:28.880 --> 00:01:32.400
You can find over 10 years of past episodes at talkpython.fm.
00:01:32.580 --> 00:01:35.780
And if you want to be part of the show, you can join our recording live streams.
00:01:36.120 --> 00:01:36.600
That's right.
00:01:36.790 --> 00:01:40.060
We live stream the raw uncut version of each episode on YouTube.
00:01:40.460 --> 00:01:45.060
Just visit talkpython.fm/youtube to see the schedule of upcoming events.
00:01:45.290 --> 00:01:48.940
Be sure to subscribe there and press the bell so you'll get notified anytime we're recording.
00:01:49.840 --> 00:01:52.380
This episode is sponsored by Sentry's Seer.
00:01:52.660 --> 00:01:55.020
If you're tired of debugging in the dark, give Seer a try.
00:01:55.680 --> 00:01:59.320
There are plenty of AI tools that help you write code, but Sentry's Seer is built to
00:01:59.320 --> 00:02:00.900
help you fix it when it breaks.
00:02:01.480 --> 00:02:07.200
Visit talkpython.fm/sentry and use the code Talk Python26, all one word, no spaces,
00:02:07.620 --> 00:02:09.340
for $100 in Sentry credits.
00:02:09.979 --> 00:02:11.720
And it's brought to you by us.
00:02:12.360 --> 00:02:15.440
Talk Python and Python Bytes both now have MCP servers.
00:02:16.040 --> 00:02:20.440
Point your AI at 10 plus years of Python episodes, transcripts, and show notes.
00:02:20.840 --> 00:02:25.160
Free. Click MCP in the nav at talkpython.fm and at Python Bytes.
00:02:25.760 --> 00:02:29.020
Hello, hello. Welcome back to Talk Python and me.
00:02:29.350 --> 00:02:30.020
Great to have you here, Joe.
00:02:30.280 --> 00:02:33.640
Hey, thanks for having me. It's nice to get back to the show.
00:02:33.930 --> 00:02:34.540
It's been a while.
00:02:35.040 --> 00:02:35.920
It has been a while.
00:02:36.220 --> 00:02:41.800
And we talked about Granian last time, the Rust-based web application server for Python.
00:02:42.200 --> 00:02:44.200
And I got to just take a moment and say thank you.
00:02:44.320 --> 00:02:45.000
We'll come back to this.
00:02:45.040 --> 00:02:49.400
But thanks for powering Talk Python and all the different things.
00:02:49.480 --> 00:02:52.680
I think across our web apps, I don't know, like 10 million requests a month.
00:02:52.780 --> 00:02:53.740
And it's been flawless.
00:02:54.920 --> 00:02:55.720
So thanks.
00:02:55.880 --> 00:02:58.420
I appreciate you serving up all the stuff for everyone.
00:02:58.680 --> 00:02:59.120
Oh, yeah.
00:02:59.340 --> 00:02:59.440
Thanks.
00:03:00.220 --> 00:03:00.820
It's been a pleasure.
00:03:00.980 --> 00:03:04.540
Like, it's nice to see, you know, when something just works.
00:03:04.980 --> 00:03:09.660
like when you do something and like at the end of the day it just works and people start using it
00:03:09.740 --> 00:03:16.560
like of course like they they ask for a bunch of features you know um but it's nice to see yeah it
00:03:16.580 --> 00:03:21.300
is really nice to see and i think it's i think it's good now it's been a while since you've been on the
00:03:21.400 --> 00:03:26.440
show a couple years i don't i didn't check the exact date when i look back maybe people are new
00:03:26.480 --> 00:03:30.419
to the show or haven't heard your prior episode or just don't remember like give us a quick
00:03:30.440 --> 00:03:37.160
introduction who you are yeah sure so i'm giovanni but everybody can call me joe i'm italian but i
00:03:37.160 --> 00:03:44.400
live in vienna i am working as a programmer in century but i'm a physicist so i think like
00:03:44.760 --> 00:03:53.360
that's pretty peculiar i've been like in the open source ecosystem uh mainly in python um probably
00:03:53.440 --> 00:04:00.560
for the last oh god I think like it's almost 20 years now um and fun fact like I actually started
00:04:00.800 --> 00:04:07.160
like um doing open source or or um contributing to open source projects even before actually
00:04:07.440 --> 00:04:16.900
working as a programmer so um yeah that's me I maintain like too many packages nowadays for like
00:04:16.959 --> 00:04:18.380
the energy I have.
00:04:20.080 --> 00:04:22.019
But yeah, that's pretty much.
00:04:22.360 --> 00:04:22.640
Excellent.
00:04:22.890 --> 00:04:25.320
Yeah, I know how that goes with the packages.
00:04:25.660 --> 00:04:26.740
It's like, oh, here's a cool idea.
00:04:26.740 --> 00:04:27.500
I want to put it out there.
00:04:27.800 --> 00:04:29.520
If it gains no traction, you can shut it down.
00:04:30.080 --> 00:04:32.000
If it gains a ton of traction, you're excited.
00:04:32.100 --> 00:04:33.280
If it gains a little traction,
00:04:33.780 --> 00:04:35.140
but enough, then you got to keep working on it.
00:04:35.200 --> 00:04:36.740
That's kind of not ideal, right?
00:04:37.080 --> 00:04:37.320
Yeah.
00:04:37.430 --> 00:04:39.060
And especially like nowadays with AI,
00:04:39.560 --> 00:04:41.440
like the amount of things you,
00:04:41.570 --> 00:04:42.520
oh, I have an idea.
00:04:43.740 --> 00:04:48.680
Like the chances like to just throw a bunch of agents into that and see what happens.
00:04:49.200 --> 00:04:49.840
It's very dangerous.
00:04:50.240 --> 00:04:50.820
It's very dangerous.
00:04:51.240 --> 00:04:52.040
I have an idea.
00:04:52.040 --> 00:04:52.840
It'll probably take me an hour.
00:04:52.880 --> 00:04:54.080
Let me see if I can validate it.
00:04:54.520 --> 00:04:57.080
Thursday afternoon, you're still like, oh, I'm still almost there.
00:04:57.120 --> 00:04:58.980
It's like, where did my week go?
00:04:59.280 --> 00:04:59.500
What happened?
00:05:00.360 --> 00:05:01.420
It's fun though, isn't it?
00:05:01.500 --> 00:05:02.360
It's a wild time.
00:05:02.720 --> 00:05:05.000
Yeah, it's weird, right?
00:05:05.200 --> 00:05:12.800
Because in a way, we have this very powerful, not super constant tool.
00:05:14.080 --> 00:05:19.760
but it's very powerful and and sometimes just amazing like sometimes like what do you what
00:05:19.760 --> 00:05:27.160
you get back is just amazing and you say like oh okay that's nice that's unexpected sometimes is
00:05:27.280 --> 00:05:34.960
the dumbest thing possible you can you you can get but i guess like the point the main point is that
00:05:35.240 --> 00:05:42.860
i think it it shows up how as humans we have like a complicated relationship with tools
00:05:43.860 --> 00:05:51.980
it can exploit a bunch of weird human behavior sometimes so yeah yeah if you like building things
00:05:52.520 --> 00:05:56.560
you know create you've got ideas and you want to see them come to life it's an unprecedented time
00:05:56.720 --> 00:06:03.220
if if you really your main joy was to be in the code working on the little bits of nuance i can
00:06:03.300 --> 00:06:07.300
see that it's frustrating and uh there's there's a whole there's a whole thing that we go into there
00:06:07.260 --> 00:06:08.120
but that's probably not.
00:06:08.130 --> 00:06:09.660
But wow, what a crazy time, right?
00:06:09.900 --> 00:06:10.900
Yeah, absolutely.
00:06:11.400 --> 00:06:14.000
Yeah, so let's jump into the topics here.
00:06:14.010 --> 00:06:18.560
I think the first thing I want to do is let's just do a little dive into Granian.
00:06:19.320 --> 00:06:21.520
That's why I reached out to you to have you on the show the first time.
00:06:21.840 --> 00:06:23.160
Like I said, it's powering.
00:06:23.390 --> 00:06:25.460
I have on the Talk Python server,
00:06:25.680 --> 00:06:29.680
there's 33 different containers running a bunch of different things,
00:06:29.770 --> 00:06:32.440
and probably about 15 of them are running Granian.
00:06:33.020 --> 00:06:33.380
Nice.
00:06:33.880 --> 00:06:34.160
Yeah.
00:06:34.800 --> 00:06:36.820
So like I said, it's been really, really cool.
00:06:37.040 --> 00:06:41.400
just tell people what is Granian because I think not only is it interesting as a thing that you've
00:06:41.400 --> 00:06:45.580
done previously and been on the show before it's also an interesting lead-in to where we're going
00:06:45.580 --> 00:06:52.940
with Tonio so what's great yeah sure uh so Granian is an application server for Python so in Python
00:06:53.540 --> 00:07:02.700
for historical reasons um we separated like the application layer from the HTTP layer so when we
00:07:02.600 --> 00:07:07.640
want to serve a web application in Python compared to other languages
00:07:07.740 --> 00:07:12.260
in which the server part is actually a part of the application.
00:07:12.640 --> 00:07:18.640
In Python, we tend to have a separated package, which is a server,
00:07:19.100 --> 00:07:22.340
and that server's import your actual application.
00:07:22.760 --> 00:07:25.820
And then we have application protocols
00:07:26.720 --> 00:07:30.120
to make the server talk with your application and back.
00:07:31.100 --> 00:07:34.960
The two main protocols in Python are VUSGI or WSGI.
00:07:35.500 --> 00:07:38.120
I never really understood how to say that,
00:07:39.720 --> 00:07:44.760
which is like the original PET333, if I'm not mistaken.
00:07:45.050 --> 00:07:48.100
So it's quite ancient in the Python history.
00:07:49.140 --> 00:07:53.860
And once we had like AsyncIO, a new protocol emerged.
00:07:54.380 --> 00:07:57.660
That's called ASGI or ASGI because, of course,
00:07:57.780 --> 00:08:00.080
it's the asynchronous server gateway.
00:08:00.520 --> 00:08:05.260
So, Granian is, again, an application server,
00:08:06.360 --> 00:08:11.720
so you can consider it as an alternative to MicroVoosgi,
00:08:13.860 --> 00:08:17.600
Uvicorn, Hypercorn, and a bunch of other servers
00:08:17.820 --> 00:08:19.240
that happened during the years.
00:08:20.520 --> 00:08:24.640
And, yeah, the peculiar fact is it's made with Rust.
00:08:26.080 --> 00:08:29.880
So, compared to all the other servers you might use in Python,
00:08:29.960 --> 00:08:34.659
all the HTTP part or the network layer
00:08:35.300 --> 00:08:39.780
is running completely out of the interpreter,
00:08:40.940 --> 00:08:46.020
which kind of alleviates the load on the interpreter,
00:08:46.230 --> 00:08:47.320
like on the Python side.
00:08:48.550 --> 00:08:50.680
And depending on the context,
00:08:50.860 --> 00:08:53.420
it might be more performant
00:08:53.660 --> 00:08:57.500
or it might stabilize your latency more.
00:08:58.660 --> 00:09:03.360
You know, I think thinking back now, I'm pretty sure that that was the reason I chose Granian.
00:09:03.770 --> 00:09:09.420
Not that it was a ton faster, but the P95 bad side of things.
00:09:09.550 --> 00:09:13.260
Like how much might it slow down under certain weird circumstances?
00:09:13.760 --> 00:09:17.800
It was way more stable than a lot of the other servers, right?
00:09:17.870 --> 00:09:19.220
And that's kind of what you're referring to.
00:09:19.820 --> 00:09:23.640
Yeah, because again, like a normal server which runs everything into Python,
00:09:23.720 --> 00:09:39.480
Like it has this kind of side effects that when your application is starting becoming slow, it kind of becomes like an infinite loop in which like the server adds additional load to the interpreter, which is already like highly loaded from your application.
00:09:39.860 --> 00:09:49.940
And so like Graniac kind of avoids all of that because like even if your application is fully loaded, like all the web and network layer is out of the interpreter.
00:09:50.100 --> 00:09:52.880
It doesn't disturb, let's say the interpreter with that.
00:09:53.200 --> 00:09:53.280
Yeah.
00:09:53.540 --> 00:09:58.080
I do want to just give a little bit of a hat tip to WSGI or WSGI.
00:09:58.450 --> 00:10:14.240
Because, yeah, you pointed out it is old, but it's really, really nice that if somebody is out there running on GUnicorn or they're running on some other server, MicroWSGI, which you should stop running MicroWSGI because it's not supported anymore.
00:10:14.600 --> 00:10:16.580
You don't have to change your code at all.
00:10:16.760 --> 00:10:20.480
You just take your flask or your Jenga or whatever, and you just say, now run here.
00:10:20.510 --> 00:10:24.740
And they just, Grandin talks WizGey or Asggy to that thing, and it's transparent.
00:10:25.330 --> 00:10:25.720
It's beautiful.
00:10:26.140 --> 00:10:26.240
Yeah.
00:10:26.510 --> 00:10:26.620
Yeah.
00:10:26.940 --> 00:10:33.659
I mean, there are like still some, you know, sometimes weird things like, like, Vertzoic
00:10:34.500 --> 00:10:37.680
has some subtleties, sometimes some nuances.
00:10:37.840 --> 00:10:50.700
So I had to like add some environment key just for Verzoic because under some circumstances, if that key is not there, things don't work exactly good.
00:10:51.120 --> 00:11:02.120
So yes, I agree that like having like the two protocols separated and coded like in a specific way gives you this ability to switch the server.
00:11:02.520 --> 00:11:08.180
On the other hand, sometimes, you know, you can be a pain in some time.
00:11:08.680 --> 00:11:09.400
Yeah, for sure.
00:11:09.840 --> 00:11:10.460
Yeah, for sure.
00:11:11.780 --> 00:11:18.380
But yeah, I'd say like we like recently it's getting more popular.
00:11:18.690 --> 00:11:27.540
So I'd say if I'm not mistaken, like a few weeks ago, I think I surpassed like HyperCorn in terms of downloads.
00:11:28.760 --> 00:11:28.980
Awesome.
00:11:29.060 --> 00:11:37.480
And yeah, it's used like in a bunch of different big companies, Microsoft, Google, for sure.
00:11:38.350 --> 00:11:40.000
We use it at Sentry, of course.
00:11:41.140 --> 00:11:46.040
And I think like Sentry is the only company in which we use like all the three supported
00:11:46.380 --> 00:11:51.300
protocols of Granian because Granian also supports like its own protocol, which is named
00:11:51.410 --> 00:11:56.320
like RSGI, I don't know, like RSGI, which is a redesign.
00:11:56.740 --> 00:11:56.940
Risky.
00:11:57.840 --> 00:11:57.940
Yeah.
00:12:00.220 --> 00:12:07.460
um so yeah i'd say it's going pretty good um i think like the last let's say this year was mostly
00:12:07.800 --> 00:12:16.340
into um not not not not mostly about features i think features but mostly about you know making
00:12:16.480 --> 00:12:22.400
the thing more stable and covering like some edge cases um especially again we're using it the
00:12:22.460 --> 00:12:28.260
Sentry. So now like if something is wrong with Grinia, like I have people knocking on my door.
00:12:28.900 --> 00:12:34.900
I bet you do. Well, I mean, with Sentry, Sentry gets an insane amount of like an unimaginable
00:12:35.040 --> 00:12:39.480
amount of traffic. I'm sure with the error reporting side of things and probably a little
00:12:39.580 --> 00:12:46.040
more so these days on the MCP and API side as well with all the agents. I know that I use the MCP
00:12:46.120 --> 00:12:50.580
and absolutely love it. It's been incredible. It's really, really good. I could just say on my
00:12:50.600 --> 00:12:54.720
project hey there's a century error i got what's up like literally that's all i gotta say in clause
00:12:54.840 --> 00:12:57.920
like hold on we're on it and it's like yeah you know five minutes later it's like here's what's
00:12:57.930 --> 00:13:02.580
going on let's work on it you know yeah really good stuff uh i do want to give a quick shout out as
00:13:02.700 --> 00:13:08.220
well uh on this regard to gradient like people are like oh it's it's not as popular as server x or
00:13:08.310 --> 00:13:12.120
whatever right although i think that's starting to fade it's it's got 5 000 stars and a ton of
00:13:12.210 --> 00:13:19.079
people using it and so on yeah but it's also based on hyper from rust which itself has uh where where's
00:13:19.100 --> 00:13:25.420
my numbers, 410,000 projects using it. So it's really tested, right? And obviously the stuff at
00:13:25.500 --> 00:13:31.640
Sentry as well. Yeah. Yeah. Like the only thing I'm sure about Granian is that like the HTTP stack,
00:13:32.100 --> 00:13:38.280
that's super safe. Like that's like, I could bet like everything I have on that. So if something
00:13:38.300 --> 00:13:44.660
is wrong in Granian, like that's on me, not on Hyper, like to be clear. This portion of Talk
00:13:44.660 --> 00:13:49.660
Python to me is brought to you by Sentry and Sear AI. There are plenty of AI tools that help you
00:13:50.080 --> 00:13:55.280
write code, but Sentry Sear is built to help you fix it when it breaks. The difference is context.
00:13:55.920 --> 00:14:00.500
Sear isn't just guessing based on syntax. It's analyzing your actual Sentry data,
00:14:00.760 --> 00:14:05.360
your stack traces, logs, and failure patterns. Because it has the full context, it can
00:14:05.740 --> 00:14:09.620
A, spot buggy code in review and help prevent issues before they happen,
00:14:10.260 --> 00:14:13.040
and B, identify the root cause of production errors.
00:14:13.980 --> 00:14:18.540
It can even draft a fix and hand the work off to an agent-like cursor to open a PR for you.
00:14:19.140 --> 00:14:21.140
Seer turns Sentry into a complete loop.
00:14:21.660 --> 00:14:24.500
You have your traces, errors, logs, and replays to see the problem,
00:14:24.800 --> 00:14:26.260
and now AI to help solve it.
00:14:26.620 --> 00:14:30.920
Join millions of devs at companies like Claude, Disney+, and even Talk Python,
00:14:31.380 --> 00:14:32.560
who use Sentry to move faster.
00:14:33.080 --> 00:14:37.860
Check them out at talkpython.fm/sentry and use code talkpython26,
00:14:38.440 --> 00:14:41.800
all one word, for $100 in Sentry credits.
00:14:42.440 --> 00:14:44.180
Thank you to Sentry for supporting Talk Python.
00:14:45.660 --> 00:14:48.980
I recently did a post over on my personal blog
00:14:49.080 --> 00:14:52.860
calling cutting Python web app memory by 31%.
00:14:52.860 --> 00:14:55.760
And I did a ton of analysis and stuff
00:14:56.660 --> 00:14:59.260
on how I was running Granian and different things,
00:14:59.300 --> 00:15:00.240
just how I was running Python.
00:15:00.820 --> 00:15:02.400
And I think this is going to be a good lead-in
00:15:02.400 --> 00:15:03.540
to where we're going as well.
00:15:03.880 --> 00:15:13.040
I got it to where I was using 1.3 gigs, let's say, for Talk Python training the courses specifically.
00:15:13.360 --> 00:15:15.160
And people are like, oh, it's just a little courses thing.
00:15:15.200 --> 00:15:19.960
Like, well, it has 178,000 lines of Python, so it's not a totally small project.
00:15:20.440 --> 00:15:23.240
But down by the end, I did a bunch of stuff on it.
00:15:24.480 --> 00:15:25.280
Lazy imports.
00:15:25.779 --> 00:15:28.660
I switched to async, which is why I brought this up.
00:15:28.940 --> 00:15:31.160
But somewhere down here, I don't know what the final number is.
00:15:31.400 --> 00:15:31.820
Here we go.
00:15:31.880 --> 00:15:36.560
In the end, I got it down to 450 megs from 0.28 gigs.
00:15:37.190 --> 00:15:40.900
One of the main reasons I did that as a way I want to kind of want to lead into like this,
00:15:41.380 --> 00:15:45.740
where we're going is I said, well, the way grading works is for each,
00:15:46.250 --> 00:15:49.540
either you can make multi-processes that are single threaded or it could do multiple threads
00:15:49.660 --> 00:15:51.380
and then it'll create a separate interpreter for each one.
00:15:51.490 --> 00:15:55.240
Either way, it kind of like replicates out the running.
00:15:55.420 --> 00:15:58.660
It'll create copies in one way or another of your code.
00:15:59.010 --> 00:16:01.760
And that, if you could run one of them, that would be great.
00:16:01.840 --> 00:16:03.000
But why do we have so many?
00:16:03.330 --> 00:16:04.900
We have so many because of the GIL.
00:16:05.010 --> 00:16:06.140
We have poor concurrency.
00:16:06.640 --> 00:16:10.360
It's really hard to handle a lot of load and like actually access, you know, my servers,
00:16:10.600 --> 00:16:10.920
eight cores.
00:16:11.010 --> 00:16:14.060
Like I could only access one core like normally, right?
00:16:14.380 --> 00:16:17.560
And so we do all these things to like explode them out.
00:16:17.680 --> 00:16:18.880
But what is the most expensive?
00:16:19.140 --> 00:16:22.160
Like five years ago, this was true on servers, but really is true now.
00:16:22.250 --> 00:16:23.000
What is most expensive?
00:16:23.360 --> 00:16:24.380
Memory, not compute, right?
00:16:25.180 --> 00:16:25.300
Yeah.
00:16:25.700 --> 00:16:25.760
Yeah.
00:16:26.160 --> 00:16:26.600
Thanks AI.
00:16:27.000 --> 00:16:27.540
Thanks AI.
00:16:27.710 --> 00:16:27.940
Thanks Altman.
00:16:28.860 --> 00:16:30.120
We really appreciate that.
00:16:32.260 --> 00:16:34.580
But we've free-threaded Python now.
00:16:34.780 --> 00:16:35.300
That's a thing.
00:16:35.740 --> 00:16:43.280
And so we're kind of moving into this world where we can take better advantage of the hardware and we don't have to multiply out.
00:16:44.220 --> 00:16:47.060
I know your process takes 500 megs of RAM, but we need four of them.
00:16:47.090 --> 00:16:49.580
So now you need two gigs just to run your code.
00:16:50.360 --> 00:16:51.940
We don't necessarily need that anymore.
00:16:52.160 --> 00:16:53.780
And grading would be a really good foundation for that.
00:16:54.000 --> 00:16:59.440
But let's just think broadly about free-threading and async as it is and stuff.
00:16:59.660 --> 00:17:02.620
I know you've thought a lot about this because of this project that you built.
00:17:03.680 --> 00:17:04.819
Well, what do you think?
00:17:05.350 --> 00:17:08.720
First thing, can I say free threading to me?
00:17:09.140 --> 00:17:11.199
It's super exciting.
00:17:11.640 --> 00:17:16.060
And I don't know why people are not as much excited as me on this.
00:17:16.819 --> 00:17:25.480
Because this is, I'd say, the biggest thing that ever happened into Python since the beginning of Python.
00:17:25.680 --> 00:17:27.140
In the last 30 years.
00:17:27.380 --> 00:17:31.720
do you remember like Python 2 to Python 3 and all like,
00:17:32.080 --> 00:17:36.360
oh, now strings are not bytes anymore and everything is Unicode?
00:17:36.700 --> 00:17:36.800
Yeah.
00:17:37.040 --> 00:17:40.960
That's nothing compared to, hey, the gil is not there anymore.
00:17:41.080 --> 00:17:46.800
Like we talk about like every Python room everywhere in the world,
00:17:46.860 --> 00:17:53.220
like at some point talked about the gil and why we have to do to have the gil, right?
00:17:53.960 --> 00:17:54.160
Right.
00:17:54.720 --> 00:17:59.220
The Gilectomy that Larry Hastings had, it seemed to perpetually be working on and never
00:17:59.440 --> 00:18:00.020
kind of went anywhere.
00:18:00.340 --> 00:18:00.480
Yeah.
00:18:00.480 --> 00:18:05.660
It took like 15 years of people talking about this and it finally landed.
00:18:05.860 --> 00:18:12.000
Like last year, yes, of course, last year it was beta because like in Python 3.13, it
00:18:12.000 --> 00:18:14.380
was like, this is an experiment still.
00:18:14.900 --> 00:18:17.840
That was the asterisk on top.
00:18:18.960 --> 00:18:22.120
But since Python 3.14, that's considered stable.
00:18:22.540 --> 00:18:28.560
It's exactly as the normal GIL-included interpreter.
00:18:30.480 --> 00:18:33.540
And at the end of the day, the biggest part of all of this
00:18:33.800 --> 00:18:38.800
was using reference counting instead of singleton.
00:18:39.160 --> 00:18:44.000
So I guess I'm excited about free-threaded Python
00:18:44.020 --> 00:18:47.840
because finally I have the language I love the most.
00:18:49.020 --> 00:18:53.180
not because of the structure, not because of types or how it's designed,
00:18:53.180 --> 00:18:57.840
but because of this awesome community that only Python language has,
00:18:58.220 --> 00:18:59.080
at least in my experience.
00:19:00.220 --> 00:19:04.080
And so finally, I can use this language with actual threads.
00:19:04.540 --> 00:19:08.460
So I'm not obliged anymore to think, oh, wait, we are in Python,
00:19:08.920 --> 00:19:13.760
so I need to use a C library or something to take advantage of my CPU,
00:19:14.480 --> 00:19:25.040
Especially given, I don't know, we are in the era of, hey, even if you buy a $200 CPU that probably has 12 cores or something.
00:19:26.380 --> 00:19:28.420
So yeah, I'm very excited.
00:19:29.700 --> 00:19:32.740
I'm confused about the lack of adoption.
00:19:33.160 --> 00:19:35.260
I would love to see more adoption in this.
00:19:35.500 --> 00:19:46.120
And of course, like there are reasons, like every native C compiled extension has to be fixed to be thread safe.
00:19:47.640 --> 00:19:53.360
And probably like on a bunch of ancient extensions, that's not simple or easy.
00:19:55.180 --> 00:19:56.240
Yeah, I don't know, man.
00:19:56.320 --> 00:19:58.100
Like I'm just too excited.
00:19:58.380 --> 00:20:00.620
Like I want people to use free thread.
00:20:00.800 --> 00:20:02.020
I know.
00:20:02.180 --> 00:20:04.260
And getting it is so much easier as well.
00:20:04.340 --> 00:20:10.840
it's just uv Python install, you know, that 3.14 T and you got it, right?
00:20:10.980 --> 00:20:11.200
Yeah.
00:20:11.600 --> 00:20:11.720
Yeah.
00:20:12.080 --> 00:20:16.740
And like, I don't know, I'm so pissed at the maintainer of the Docker images
00:20:16.880 --> 00:20:20.240
because we don't have like an official 3.14 free threaded image.
00:20:20.360 --> 00:20:20.660
Like why?
00:20:21.160 --> 00:20:21.220
Why?
00:20:21.400 --> 00:20:23.460
Oh yeah, that is actually a good point.
00:20:23.580 --> 00:20:28.900
Like it has been a year and a half and it just requires putting the T on the uv installed bit.
00:20:29.160 --> 00:20:29.440
Yeah.
00:20:29.740 --> 00:20:29.860
Yeah.
00:20:30.320 --> 00:20:30.460
Yeah.
00:20:30.480 --> 00:20:36.660
Yeah, but again, to my perspective, free-threaded Python opens the window to do a bunch of...
00:20:36.840 --> 00:20:42.040
First, to solve a bunch of the issues you mentioned, which is, for example, in Centri,
00:20:42.160 --> 00:20:43.980
the number one issue is...
00:20:44.120 --> 00:20:50.460
I mean, Centri at the end of the day is this 18-years-old Django monolith.
00:20:51.420 --> 00:20:59.520
And so, yes, we deploy in several multiple different deployments, activating just some parts.
00:20:59.720 --> 00:21:20.080
But even so, like, yeah, we have the same problem, which is, hey, like, in order to use like one C view out of like a VM that GCP gives us, like, yeah, we need to spawn like four different processes and require like, I don't know, four, five, six gigabytes of memory for like a single pod.
00:21:20.320 --> 00:21:31.320
And not only do you have to do that, you also have to, anytime there's some sort of in-memory cache, like an LRU cache or something, every one of those has to be spun up for all four, not just for one, right? There's a lot of powerful sharing.
00:21:31.440 --> 00:21:34.120
Do we want to start talking about observability?
00:21:34.420 --> 00:21:36.580
Because you cannot just use like
00:21:36.640 --> 00:21:39.380
the Prometheus SDK or anything like that.
00:21:40.000 --> 00:21:42.000
Because again, like then those became like
00:21:42.480 --> 00:21:43.340
four different instances.
00:21:43.600 --> 00:21:45.800
So you cannot even track like the same metrics
00:21:46.620 --> 00:21:48.480
unless you do like some weird tricks.
00:21:48.960 --> 00:21:53.200
Like, yeah, relying on the multiprocessing library.
00:21:54.180 --> 00:21:54.800
It's messy, man.
00:21:54.980 --> 00:21:57.080
So like, yeah, I think like
00:21:57.620 --> 00:22:00.220
FreeThreader like solves a bunch of problems,
00:22:00.820 --> 00:22:04.180
Especially in web development in Python.
00:22:05.420 --> 00:22:09.780
Because everybody says like, oh yeah, they did this just because of AI, you know,
00:22:09.880 --> 00:22:12.240
because they can like make things parallel, whatever.
00:22:12.720 --> 00:22:14.200
I don't get that.
00:22:14.300 --> 00:22:18.280
Like, like we have like this amazing feature and it solves a bunch of problem
00:22:18.460 --> 00:22:23.180
on web development and also like it opens up the a bunch of new opportunities.
00:22:23.400 --> 00:22:27.680
Because again, like one of the most annoying parts in Granian, which is also
00:22:27.700 --> 00:22:33.400
Like one of the things that people opens like the most, the vast majority of issues about
00:22:33.580 --> 00:22:38.960
is because nobody understands like how socket sharing works across different processes.
00:22:39.370 --> 00:22:45.100
And again, like with FreeThreader, like that's just go away if we have something different
00:22:45.230 --> 00:22:45.940
from a Synco.
00:22:46.310 --> 00:22:51.280
And I guess that's the, that's when we get on the point here.
00:22:52.520 --> 00:22:57.660
Because for example, like in Grainium, so Grainium supports FreeThreader Python scenes
00:22:57.680 --> 00:22:59.440
2.0, if I'm not mistaken.
00:22:59.920 --> 00:23:01.520
And so when you use
00:23:01.700 --> 00:23:03.420
Granian on a free-threaded interpreter,
00:23:03.840 --> 00:23:05.520
workers become
00:23:07.600 --> 00:23:07.660
threads.
00:23:08.200 --> 00:23:09.340
They're no longer processes.
00:23:10.200 --> 00:23:11.520
And I can skip all the dance
00:23:11.620 --> 00:23:13.160
of, yeah, open the socket,
00:23:14.140 --> 00:23:15.500
but just bind
00:23:15.500 --> 00:23:17.440
on that, but not on listen, because then
00:23:17.640 --> 00:23:19.440
it gets to be shared across processes
00:23:19.960 --> 00:23:21.500
and so each worker can have his own
00:23:21.660 --> 00:23:23.540
backlog. It's a mess, man.
00:23:23.740 --> 00:23:25.700
So on the free-threaded variant,
00:23:26.200 --> 00:23:28.100
Like, Grenian already, like, used threads.
00:23:29.799 --> 00:23:35.280
The subtlety is that each thread has its own event loop.
00:23:35.510 --> 00:23:41.280
So what Grenian cannot do today is sharing things between those threads.
00:23:41.560 --> 00:23:49.180
At the end of the day, yes, the application is shared, but, like, every request, like, if a repus comes to a worker, that's it.
00:23:49.250 --> 00:23:50.120
Like, there's no...
00:23:50.310 --> 00:23:51.780
You cannot cross boundaries, right?
00:23:52.060 --> 00:23:54.040
when you await for something,
00:23:54.130 --> 00:23:55.260
when you suspend for something,
00:23:56.440 --> 00:23:57.500
that's the loop,
00:23:57.750 --> 00:23:59.020
the event loop you're running into.
00:23:59.980 --> 00:24:01.540
Which is a big limitation for servers.
00:24:02.300 --> 00:24:03.320
Yeah, and one is calling await
00:24:03.540 --> 00:24:04.400
and another is calling await
00:24:04.680 --> 00:24:06.060
or maybe doing some work,
00:24:06.260 --> 00:24:07.860
but if they were the same event loop,
00:24:08.220 --> 00:24:09.180
one thing could run
00:24:09.420 --> 00:24:10.240
because the other is awaiting,
00:24:10.380 --> 00:24:12.400
but they're actually separate altogether, right?
00:24:12.560 --> 00:24:15.460
Yeah, and also you can still end up
00:24:15.460 --> 00:24:16.720
in these weird conditions
00:24:16.930 --> 00:24:19.360
in which maybe, I don't know,
00:24:19.440 --> 00:24:21.139
50% of your connection
00:24:21.740 --> 00:24:24.640
we're bound to a specific event loop.
00:24:25.180 --> 00:24:28.080
And so you also have like unbalancement
00:24:28.190 --> 00:24:29.420
between like the different threads.
00:24:29.510 --> 00:24:30.640
So you have like one thread
00:24:30.800 --> 00:24:32.880
that receives the vast majority of things,
00:24:33.060 --> 00:24:34.100
the other thread doing nothing.
00:24:35.480 --> 00:24:35.640
Yeah.
00:24:35.860 --> 00:24:36.000
Yeah.
00:24:36.390 --> 00:24:38.380
Let's say like we have new opportunities.
00:24:39.200 --> 00:24:41.080
We kind of lack some libraries,
00:24:41.370 --> 00:24:42.700
like some libraries behavior
00:24:43.700 --> 00:24:45.620
that can actually give us like
00:24:45.880 --> 00:24:47.580
all the actual meat here.
00:24:48.600 --> 00:24:48.900
Agreed.
00:24:49.020 --> 00:24:53.600
I want to talk about one area before we dive in, the Tone.io,
00:24:53.820 --> 00:24:55.740
and that's just asyncio event loops.
00:24:56.120 --> 00:24:57.460
And I know during your presentation,
00:24:57.700 --> 00:25:00.240
so you gave a talk on this at EuroPython this year,
00:25:00.340 --> 00:25:04.120
and I'll link to the topic right here in the moment.
00:25:05.140 --> 00:25:06.920
But there are people like, oh, well, we have uvloops,
00:25:07.040 --> 00:25:08.300
so doesn't that kind of solve it?
00:25:08.500 --> 00:25:10.980
And I think that's a really interesting thought
00:25:11.180 --> 00:25:14.080
because without calling out anybody, I didn't intend to call out anybody.
00:25:14.240 --> 00:25:18.540
I think it just highlights in the Python space,
00:25:18.940 --> 00:25:22.240
There's just not a lot of thinking about what concurrency means,
00:25:22.550 --> 00:25:24.660
why it matters, how you program for it.
00:25:25.460 --> 00:25:26.760
And that's going to be a challenge.
00:25:27.020 --> 00:25:28.620
I think that's part of the challenge of what you're talking about
00:25:28.660 --> 00:25:30.180
with the adoption of free-threaded Python.
00:25:30.330 --> 00:25:34.380
But I think also it is a challenge in the sense that
00:25:34.720 --> 00:25:37.520
thinking about making the event loop faster by, I don't know,
00:25:37.720 --> 00:25:39.900
20% or whatever uvloop actually does, which is great,
00:25:40.100 --> 00:25:43.300
it doesn't really address the fact that you're still only on one thread.
00:25:43.800 --> 00:25:47.400
Your event loop can access, like, I just, against probably better judgment,
00:25:47.600 --> 00:25:51.120
I just bought a Mac Studio Max, which has 18 cores.
00:25:51.559 --> 00:25:55.700
If I write one thread, that's about like 5% of the capacity of that machine.
00:25:56.300 --> 00:26:01.600
So what does it matter if it's 4.9% or 5.1% depending on which loop?
00:26:01.840 --> 00:26:02.520
I don't care.
00:26:02.640 --> 00:26:04.120
That is broken either way.
00:26:04.280 --> 00:26:06.400
I want 95% capacity.
00:26:06.780 --> 00:26:07.080
Yes.
00:26:07.280 --> 00:26:07.560
Right?
00:26:07.940 --> 00:26:08.920
And that's, I don't know.
00:26:08.960 --> 00:26:11.940
I thought it would be worthwhile to kind of like talk about uvloop a little bit.
00:26:12.020 --> 00:26:12.940
And you built our loop.
00:26:13.100 --> 00:26:14.860
And those things are great in their building blocks.
00:26:14.960 --> 00:26:15.100
Yeah.
00:26:15.180 --> 00:26:16.820
They're not the panacea.
00:26:16.920 --> 00:26:17.440
They're not the fix.
00:26:17.860 --> 00:26:25.900
Yeah, and we also now have Marcelo Trzesitsky, the maintainer of UVCore and Starlet.
00:26:27.680 --> 00:26:39.500
It has recently published Zoovloop, which is another event loop for Python relying on libUV, but it's written in Zig.
00:26:40.900 --> 00:26:44.280
But yeah, as you said, all of these event loops,
00:26:46.220 --> 00:26:52.580
yes, they can optimize some of the hot part of the cycle.
00:26:53.760 --> 00:26:55.320
But basically, that's it.
00:26:55.820 --> 00:27:00.960
Nothing really changed the shape of how things work.
00:27:02.080 --> 00:27:07.939
And so, yes, for sure, you should actually install
00:27:08.100 --> 00:27:13.780
uvloop or r loop or zoo v loop or whatever to to speed up like because that's performance you're
00:27:13.820 --> 00:27:19.320
leaving on the table regardless right like so uh it's good to have this project because they also
00:27:19.480 --> 00:27:26.500
like can light uh some of the inefficiencies of a sink io in my opinion um so i think it's still
00:27:26.550 --> 00:27:31.960
good to have these projects and you should use them um but yeah they want like the best you can
00:27:31.920 --> 00:27:39.520
GAT is probably like in the order of like 20% in, and we're talking about like row throughput
00:27:39.940 --> 00:27:46.180
if you're just doing TCP and nothing else, which is nobody does like, like nothing does
00:27:46.380 --> 00:27:47.020
nothing out there.
00:27:47.180 --> 00:27:47.240
Right?
00:27:47.320 --> 00:27:48.800
Like we do a bunch of stuff.
00:27:49.160 --> 00:27:49.400
Yeah.
00:27:49.480 --> 00:27:52.980
If you construct an example where basically all you're doing is waiting very efficiently,
00:27:53.410 --> 00:27:55.180
then you start to see those, those deviate.
00:27:55.180 --> 00:27:59.460
And also if you've got, like, I've got 10 tasks that run over two seconds.
00:27:59.940 --> 00:28:00.660
It does zero.
00:28:00.690 --> 00:28:01.220
It doesn't matter.
00:28:01.600 --> 00:28:05.080
This only matters when you're doing very, very fine-grained work and tons of it,
00:28:05.080 --> 00:28:07.900
and there's lots of switching and that sort of thing, right?
00:28:08.160 --> 00:28:09.080
Yes, yes.
00:28:11.120 --> 00:28:16.140
And so, yes, I guess this was one of my realizations.
00:28:16.570 --> 00:28:20.300
Because, again, I worked on ArtLoop mainly last year.
00:28:21.470 --> 00:28:24.900
And working on ArtLoop made me realize two things.
00:28:25.860 --> 00:28:31.260
The first thing is that asimkayo, it's overcomplicated sometimes.
00:28:31.540 --> 00:28:39.160
It's so fun fact at the presentation I did about that loop in Python Italy 25.
00:28:40.450 --> 00:28:51.900
I had a slide asking to the audience, what's the difference between a protocol and a transport in Async.io.
00:28:52.220 --> 00:29:01.300
Because if you check the official documentation of Python and you get to that chapter, you will find out that they have like to try to explain you that difference.
00:29:01.860 --> 00:29:05.280
they try to do that like in three different ways,
00:29:06.340 --> 00:29:07.720
which to my experience,
00:29:07.880 --> 00:29:10.320
like if you need three different ways
00:29:10.380 --> 00:29:11.620
to explain the same thing,
00:29:12.020 --> 00:29:13.800
something is deeply wrong.
00:29:14.620 --> 00:29:17.860
Definitely violates an Einstein core philosophy.
00:29:18.180 --> 00:29:18.360
Yes.
00:29:19.260 --> 00:29:20.400
Also because like, again,
00:29:20.660 --> 00:29:22.460
if we think about the Zen of Python,
00:29:23.140 --> 00:29:25.280
like simple is better than complex, right?
00:29:27.000 --> 00:29:29.440
So that was my first realization, right?
00:29:30.440 --> 00:29:35.480
Because, again, like you have a bunch of primitives, like you have events, futures.
00:29:37.400 --> 00:29:43.880
Oh, by the way, every time they get spawned into the event loop, they get wrapped into a task.
00:29:44.200 --> 00:29:45.780
What is a task? Nobody knows.
00:29:46.920 --> 00:29:51.220
Oh, and by the way, when they actually run inside the event loop, they became handles.
00:29:52.380 --> 00:29:59.820
So we have a bunch of this weird thing that they're not really exposed to you if you just write async code.
00:30:00.180 --> 00:30:06.020
But if you need to debug something when something is wrong, then you need all of that to understand what's going on, right?
00:30:06.800 --> 00:30:14.120
And the other realization, again, was, okay, I reached a point in which there's no other possible optimization in our book.
00:30:14.300 --> 00:30:20.620
Because the vast majority of CPU time spent here is around AsyncIO primitives.
00:30:21.000 --> 00:30:22.560
And I have one single thread.
00:30:22.960 --> 00:30:28.920
And so, like, the only, when I came to realize this, I just said, okay, wait a second.
00:30:29.100 --> 00:30:35.020
Is that, is really that hard to start like scratch whiteboard?
00:30:35.420 --> 00:30:40.380
Like from the beginning, like let's just assume for a second, like asyncio never existed.
00:30:40.720 --> 00:30:42.920
And I want to do like async stuff in Python.
00:30:43.160 --> 00:30:43.860
What that look like?
00:30:43.980 --> 00:30:47.300
Like what, what does it mean to make like an asynchronous runtime in Python?
00:30:47.540 --> 00:30:48.100
Is that hard?
00:30:48.420 --> 00:30:55.119
And I don't know, like, I guess three or four weeks after this, I just got like an asynchronous
00:30:55.140 --> 00:30:57.040
runtime, a complete alternative
00:30:57.240 --> 00:30:59.040
asynchronous runtime running on Python with
00:30:59.220 --> 00:30:59.700
multiple threads.
00:31:01.280 --> 00:31:03.080
To be clear on free-threaded Python, right?
00:31:03.320 --> 00:31:05.160
Yes, free-threaded Python only because
00:31:05.650 --> 00:31:07.160
again, to have like real threads
00:31:07.530 --> 00:31:09.260
I mean, the design is to have
00:31:09.400 --> 00:31:11.120
real threads, so the moment you have
00:31:11.120 --> 00:31:12.860
the gill, I say, no, no, no,
00:31:13.200 --> 00:31:15.060
runtime error, change your
00:31:15.280 --> 00:31:17.200
interpreter because this is not good,
00:31:17.380 --> 00:31:17.520
right?
00:31:19.200 --> 00:31:21.080
This portion of Talk Python I May is brought to you by
00:31:21.300 --> 00:31:23.040
our AI tools. You know that
00:31:23.180 --> 00:31:25.100
thing where you ask an AI something about
00:31:25.120 --> 00:31:30.860
Python and it confidently tells you about a library version from 18 months ago? Well, we fixed that,
00:31:31.100 --> 00:31:37.480
at least for our shows. Talk Python and Python Bytes both have MCP servers now. Connect Talk Python
00:31:37.530 --> 00:31:43.780
and your AI can search over 550 episodes, full transcripts, every guest in the entire course
00:31:44.120 --> 00:31:49.820
catalog. Connect Python Bytes and you get almost 500 episodes of Python news going back to 2026,
00:31:50.520 --> 00:31:54.940
including every link we've ever put in the show notes. That means you can say things like,
00:31:55.060 --> 00:32:02.460
ask Talk Python what astral joining OpenAI means for uv or what has Python bytes said about uv
00:32:02.860 --> 00:32:06.740
and get real answers with real links, not hallucination. Name the show in your prompt
00:32:06.920 --> 00:32:12.900
and your AI knows exactly where to look. And if you live in the terminal, Talk Python also has a CLI
00:32:12.930 --> 00:32:20.900
too. One line, uv tool install talk-python-cli and then search episodes, transcripts, guests,
00:32:21.220 --> 00:32:26.880
courses without ever opening a browser. It's open source and it outputs text, JSON, and Markdown,
00:32:26.960 --> 00:32:31.980
so it feeds the AI tools that don't speak MCP yet. And here's the real reason I built it.
00:32:32.460 --> 00:32:38.320
Both shows cover around 10 years of Python history. The people, the decisions, the packages that took
00:32:38.380 --> 00:32:45.460
over, and the ones that quietly didn't. This enhanced access is free. No account, no API key,
00:32:45.560 --> 00:32:52.140
nothing to buy. This history should belong to all of us. Visit talkpython.fm and Python Bytes and
00:32:52.260 --> 00:32:56.840
click the MCP link in the nav bar. Connect them right now to your agents so that they'll be
00:32:57.080 --> 00:33:02.440
accessible anytime you need them in the future. And so yeah, that was like kind of the landscape
00:33:02.980 --> 00:33:09.180
in which I started building Tonio or Tonio. You can pronounce it like however you want. Like I'm
00:33:09.300 --> 00:33:13.540
Italian, so I say Tonio, of course. I think obviously, well, you have to do Tonio.
00:33:14.780 --> 00:33:15.980
So it's really good.
00:33:16.140 --> 00:33:21.900
But as it pairs to asyncio, tone IO also kind of like as a hat to that, right?
00:33:22.180 --> 00:33:22.300
Yeah.
00:33:22.560 --> 00:33:22.660
Yeah.
00:33:23.280 --> 00:33:24.040
But we have Trio.
00:33:24.460 --> 00:33:26.760
So that's not Trio.
00:33:28.020 --> 00:33:30.280
Trio is living there a little bit as well.
00:33:30.660 --> 00:33:30.740
Yeah.
00:33:30.800 --> 00:33:33.700
So this is a really interesting project that you have.
00:33:33.880 --> 00:33:37.140
And it basically says, what if we actually had threads?
00:33:37.560 --> 00:33:44.000
One of the things that's endlessly frustrated me about Python and asyncio and consequently
00:33:44.020 --> 00:33:48.220
I think the language implementation of async await is it's really good.
00:33:48.320 --> 00:33:49.200
I think, you know,
00:33:49.840 --> 00:33:52.940
the way it sort of turns async code into what looks like structurally
00:33:54.299 --> 00:33:57.060
serializable code or serial code is really, really nice.
00:33:57.320 --> 00:33:58.160
But the,
00:33:58.620 --> 00:34:02.800
the fact that the developer has to juggle loops and which loop and that loop.
00:34:02.920 --> 00:34:03.940
No, that's not the right loop.
00:34:04.240 --> 00:34:07.200
Like so many times I've been, Oh, I want to do this request.
00:34:07.640 --> 00:34:10.659
Oh, are you using court or an async web framework? You,
00:34:10.879 --> 00:34:12.840
there is an async loop, but not that one.
00:34:12.899 --> 00:34:14.720
you need the one created by the web framework.
00:34:14.860 --> 00:34:17.460
Like, oh, did you initialize the async database connection
00:34:18.240 --> 00:34:19.700
before it ran the web thing?
00:34:19.720 --> 00:34:21.100
And like, nope, that's the wrong one.
00:34:21.500 --> 00:34:24.320
And there's all this weird juggling that's just so janky.
00:34:25.000 --> 00:34:26.659
And I'm pretty sure it's janky
00:34:27.240 --> 00:34:28.940
because we didn't have true threads.
00:34:29.460 --> 00:34:31.500
And the possibility, oh, what if you crisscross this?
00:34:32.160 --> 00:34:33.340
Everything sort of falls apart.
00:34:33.540 --> 00:34:35.540
And if you could just say, look, it's just multi-threaded.
00:34:35.760 --> 00:34:37.740
And there is a thing that called the loop
00:34:37.780 --> 00:34:38.840
and that's where stuff runs.
00:34:39.300 --> 00:34:41.080
And I don't care who started it.
00:34:41.220 --> 00:34:41.760
It's our loop.
00:34:41.899 --> 00:34:47.020
When I need a loop, the runtime Python itself has a loop for me and I'll use it.
00:34:47.320 --> 00:34:47.419
Right.
00:34:47.720 --> 00:34:49.639
And I feel like you sort of took that philosophy a little bit.
00:34:49.679 --> 00:34:49.800
Right.
00:34:50.320 --> 00:34:50.480
Yeah.
00:34:50.639 --> 00:34:58.180
I'd say like the number one inspiration for Sonio is Tokyo, which is the number one Rust
00:34:58.460 --> 00:34:59.100
asynchronous runtime.
00:35:00.620 --> 00:35:03.600
That's probably because like I spend like in Rust a bunch of time.
00:35:04.900 --> 00:35:09.900
So yeah, like, I don't know, for example, like the weird thing to me is like, we still like,
00:35:09.980 --> 00:35:14.020
if you just want to do like an asynchronous program in Python,
00:35:14.410 --> 00:35:18.160
like a program, like let's say a script or, I don't know,
00:35:18.320 --> 00:35:21.680
like a simple CLI, we still need to do like a synch.io run.
00:35:22.320 --> 00:35:22.920
Like why?
00:35:23.800 --> 00:35:28.080
Like the, yeah, the amount of things you need to know
00:35:28.680 --> 00:35:33.220
and decide and stuff there is like completely different
00:35:33.440 --> 00:35:34.340
from other languages.
00:35:34.520 --> 00:35:34.600
Yeah.
00:35:34.690 --> 00:35:36.860
Because again, like think about JavaScript.
00:35:37.670 --> 00:35:38.520
You don't decide anything.
00:35:38.580 --> 00:35:40.180
Like that's async in JavaScript.
00:35:40.900 --> 00:35:41.260
That's true.
00:35:41.640 --> 00:35:43.140
Well, I'll tell you a little bit why you can't just,
00:35:43.460 --> 00:35:44.560
why you still got to call async run
00:35:44.720 --> 00:35:46.460
because there's like a foundational layer
00:35:46.980 --> 00:35:47.740
that's not present.
00:35:48.300 --> 00:35:50.120
And it's your job to write the foundation
00:35:50.420 --> 00:35:53.580
on which asyncio executes, right?
00:35:53.960 --> 00:35:55.080
Like it's still your job to figure,
00:35:55.220 --> 00:35:56.820
okay, how do I actually create a loop?
00:35:56.880 --> 00:35:57.640
How do I run a loop?
00:35:57.740 --> 00:35:59.120
How do I, like, it's your job.
00:35:59.520 --> 00:36:01.340
You can't just create an async def method
00:36:01.500 --> 00:36:04.100
and call it because there's nowhere for it to go
00:36:04.280 --> 00:36:05.440
until you go create, you know what I mean?
00:36:05.540 --> 00:36:06.120
There's like, there's just,
00:36:06.800 --> 00:36:08.660
But the foundation isn't quite there.
00:36:08.960 --> 00:36:10.800
But again, it's an entry point, right?
00:36:11.700 --> 00:36:14.080
So in Tonio, for example, yes, you have Tonio run.
00:36:14.210 --> 00:36:18.120
You want to instruct everything and configure the runtime and do whatever you want.
00:36:18.180 --> 00:36:19.340
Yes, you can do that.
00:36:19.550 --> 00:36:20.800
You don't want to do that.
00:36:21.060 --> 00:36:23.600
You have a single decorator, which is Tonio main.
00:36:24.020 --> 00:36:30.780
You put it on your function, your entry point in your program, and it's done.
00:36:32.500 --> 00:36:37.300
Yeah. All right. Before we talk about that, before we get too much in the weeds of it,
00:36:37.400 --> 00:36:43.480
why don't you just give us like, talk us through writing a program in this. There's also two ways,
00:36:43.580 --> 00:36:47.920
like maybe you could, after you talk about this, you could like sort of get into the,
00:36:48.380 --> 00:36:55.600
what this concept of colored functions versus not, and sometimes call async code viral code. And like,
00:36:55.680 --> 00:37:02.360
let's just like talk through the differences here. Yeah. So, okay. Yes. Tonyo has two
00:37:02.360 --> 00:37:06.940
supports two different syntax modes.
00:37:08.030 --> 00:37:11.960
And that's because, again, when I designed this,
00:37:12.580 --> 00:37:15.180
given that I just throw everything away
00:37:15.670 --> 00:37:16.620
and started from scratch,
00:37:17.290 --> 00:37:18.820
I ended up having a system
00:37:19.150 --> 00:37:23.580
which didn't really require async await syntax
00:37:23.730 --> 00:37:26.440
or a notation because at the end of the day,
00:37:26.550 --> 00:37:27.760
behind async await notation,
00:37:28.060 --> 00:37:29.740
those are generated, right?
00:37:30.740 --> 00:37:45.840
And so given the effort to support like two different syntax was like minimal, writing everything from scratch, I ended up like leaving up to the final user to decide, hey, you dislike async await temptation?
00:37:46.460 --> 00:37:55.400
There's still plenty of people that like, I don't know why, but they argue like all the time about, oh, you know, like I have to put async everywhere and a wait everywhere, whatever.
00:37:56.180 --> 00:37:57.560
OK, you don't like that.
00:37:58.000 --> 00:37:58.320
Guess what?
00:37:58.640 --> 00:38:00.980
Tonya also have just a yield syntax.
00:38:01.200 --> 00:38:10.240
So instead of await, you just yield from the next coroutine you want to suspend for or wait for.
00:38:11.320 --> 00:38:11.800
And that's it.
00:38:11.800 --> 00:38:15.460
You don't need to write async def everywhere.
00:38:17.480 --> 00:38:19.960
To be here is still colored, right?
00:38:20.140 --> 00:38:26.020
Because the moment you have a generator function, then whatever it calls it before has to be a generator.
00:38:26.080 --> 00:38:33.060
as well. But I guess my point was mainly like, okay, to support the syntax doesn't really take
00:38:33.210 --> 00:38:40.160
that much of a work. And once it was settled in, I mean, it's there and people can decide by,
00:38:40.580 --> 00:38:48.120
hey, you dislike one notation? Sure, just use the other one. But yes, let's say for people
00:38:48.580 --> 00:38:55.220
familiar with AsyncIO, the syntax, like the AsyncAwait syntax is very similar to AsyncIO.
00:38:55.900 --> 00:39:06.540
So you basically have your coroutine, so your sync dev, whatever you want, and you await primitives or stuff.
00:39:08.380 --> 00:39:20.800
So the main difference is that instead of importing stuff from a sync.io, so like, I don't know, sleep or timer or whatever, you import similar primitives from Tonio.
00:39:21.440 --> 00:39:28.080
We, of course, have way less primitives because, again, like, as I said before, AsyncIO has too many primitives.
00:39:29.380 --> 00:39:39.660
And the only big difference from AsyncIO, let's say, native people is that you have, like, spawn methods.
00:39:40.260 --> 00:39:41.879
So instead of saying...
00:39:43.460 --> 00:39:47.720
So in AsyncIO, when you want to make things,
00:39:48.200 --> 00:39:50.200
like spawn several tasks
00:39:50.640 --> 00:39:52.380
and then wait for all of them to complete,
00:39:52.680 --> 00:39:53.500
you usually do...
00:39:53.500 --> 00:39:56.080
You have different ways of doing this, right?
00:39:56.260 --> 00:39:58.620
You can have a task set.
00:39:59.240 --> 00:40:02.780
You can have scopes from Trio.
00:40:04.340 --> 00:40:06.060
But I mean, you can use gather
00:40:06.300 --> 00:40:08.980
so you can create tasks and then gather.
00:40:10.380 --> 00:40:10.840
with SyncIO.
00:40:10.920 --> 00:40:12.800
So Tonio is like,
00:40:12.980 --> 00:40:14.360
to do this is,
00:40:14.820 --> 00:40:16.420
you just have like two methods.
00:40:16.500 --> 00:40:17.160
You either spawn
00:40:19.000 --> 00:40:19.740
asynchronous stuff
00:40:19.980 --> 00:40:21.200
or you spawn something
00:40:21.200 --> 00:40:21.900
that is blocking
00:40:22.460 --> 00:40:23.480
and then the primitive
00:40:24.400 --> 00:40:25.920
is spawn blocking.
00:40:26.980 --> 00:40:28.140
The other major difference
00:40:28.400 --> 00:40:29.000
from a SyncIO
00:40:29.080 --> 00:40:30.680
is that when you spawn something
00:40:31.180 --> 00:40:33.000
that gets run immediately.
00:40:34.560 --> 00:40:35.940
Whereas like in a SyncIO
00:40:35.960 --> 00:40:37.080
this is true only
00:40:37.340 --> 00:40:38.440
if you create a task.
00:40:38.960 --> 00:40:41.380
Otherwise, everything else is eager.
00:40:41.960 --> 00:40:45.900
So in order to run whatever you want to wait,
00:40:45.980 --> 00:40:46.980
you need to await.
00:40:47.300 --> 00:40:49.600
So in AsyncIO, the vast majority of operations
00:40:50.540 --> 00:40:54.100
bound together the launching the operation
00:40:54.380 --> 00:40:55.440
and waiting for the result.
00:40:55.740 --> 00:40:58.320
Unless you create a task that starts immediately
00:40:58.720 --> 00:40:59.880
and then you await later, right?
00:41:00.820 --> 00:41:02.640
In Tonya, everything is wrapped around the spawn
00:41:02.840 --> 00:41:05.440
because you can call spawn and forget about it
00:41:05.660 --> 00:41:08.760
or you can await Tonya spawn to wait for the results, right?
00:41:08.900 --> 00:41:12.000
Or you can park the result of the spawn, the join, and join later.
00:41:12.980 --> 00:41:16.640
So that's, I don't know if, like, maybe it's more confusing than,
00:41:17.380 --> 00:41:19.860
it's hard to explain it simply.
00:41:20.260 --> 00:41:24.680
Maybe, but one of the challenges I've seen with standard asyncIO
00:41:24.780 --> 00:41:28.560
is you want to create a bunch of work and let it run,
00:41:28.820 --> 00:41:30.340
and then you want to get the answers back.
00:41:30.520 --> 00:41:33.140
So a naive way would be, like, call a bunch of stuff,
00:41:34.140 --> 00:41:35.980
and then when you call await,
00:41:36.380 --> 00:41:38.980
It's like you've got to create them not started
00:41:39.280 --> 00:41:40.440
and then start all of them
00:41:40.530 --> 00:41:42.500
and then go through each one and await them.
00:41:42.730 --> 00:41:44.520
Whereas this way, you get a list back
00:41:44.590 --> 00:41:45.680
and you just await them in order
00:41:45.990 --> 00:41:47.640
or await one after another somehow.
00:41:48.020 --> 00:41:49.380
They're already started, right?
00:41:49.470 --> 00:41:51.220
And so there's like the skip of this like,
00:41:51.330 --> 00:41:52.400
okay, I've got all the things
00:41:52.540 --> 00:41:53.540
that are going to become tasks,
00:41:53.660 --> 00:41:54.780
but I got to turn them into tasks
00:41:54.900 --> 00:41:56.180
so then I can await them
00:41:56.320 --> 00:41:57.400
because if I regularly await them,
00:41:57.520 --> 00:41:58.980
it'll still fall back to serial
00:41:59.500 --> 00:42:01.080
just running on the event loop, you know?
00:42:01.340 --> 00:42:01.440
Yeah.
00:42:01.730 --> 00:42:02.920
And I mean, like in Tanya is the same.
00:42:02.990 --> 00:42:04.260
Like if you await a coroutine,
00:42:04.370 --> 00:42:06.340
it means like I want to wait
00:42:06.360 --> 00:42:08.360
that to happen, right? If you don't want to
00:42:08.460 --> 00:42:10.300
just spawn, park
00:42:10.500 --> 00:42:12.220
the result of spawn into a
00:42:12.460 --> 00:42:14.220
variable, await later. I think
00:42:14.340 --> 00:42:15.120
there's an example
00:42:16.420 --> 00:42:17.480
down below in the page
00:42:19.240 --> 00:42:19.720
about
00:42:20.100 --> 00:42:21.960
spawning something and awaiting later.
00:42:24.520 --> 00:42:24.780
Somewhere.
00:42:26.220 --> 00:42:27.420
There's a bunch of documentation.
00:42:28.220 --> 00:42:28.960
Spawning there, yeah.
00:42:30.480 --> 00:42:31.760
But yeah, the idea
00:42:32.200 --> 00:42:34.000
is, and again, I didn't invent
00:42:34.040 --> 00:42:34.820
anything because
00:42:34.840 --> 00:42:41.240
the syntax. Yes, this is the example. So in this example, we compute like numbers,
00:42:41.550 --> 00:42:46.160
like stupid example, but it is to give the point. So we start like two functions,
00:42:46.850 --> 00:42:53.080
which computes numbers. Then we await for third function. So we immediately wait for the third
00:42:53.380 --> 00:43:00.260
result. And then we wait for the first two results. So in the meantime, so when you do the first poem,
00:43:00.660 --> 00:43:05.560
those two functions start already, like immediately at that point in your code,
00:43:06.660 --> 00:43:08.180
which kind of makes sense if you think about it,
00:43:08.230 --> 00:43:12.400
because it's like syntax-based, like you're invoking those coroutines, right?
00:43:12.980 --> 00:43:14.980
In that moment, you just don't await for them.
00:43:15.960 --> 00:43:20.820
And so at the end, like you just await for the parallel, actual parallel result.
00:43:21.220 --> 00:43:25.280
Like everything here is concurrent, but the only point in which you have parallel code,
00:43:25.580 --> 00:43:30.140
the actual parallel code is when you await for parallel, right?
00:43:30.920 --> 00:43:34.160
So that's the major difference from a Synco and Tonio.
00:43:35.000 --> 00:43:39.340
When you spawn stuff, that thing happened in parallel.
00:43:39.700 --> 00:43:43.320
So by default, Tonio starts with the number of threads
00:43:44.140 --> 00:43:46.340
equal to the number of your CPUs.
00:43:47.700 --> 00:43:52.980
The blocking, Tonio also has a blocking pool for blocking stuff.
00:43:54.360 --> 00:43:54.860
But that's separate.
00:43:54.900 --> 00:43:59.200
Let's say the main work loop that runs your code,
00:44:00.460 --> 00:44:04.280
by default, you have X threads per CPU cores.
00:44:05.380 --> 00:44:09.800
So if you run this on your M3 Max, M3 Studio Max,
00:44:10.740 --> 00:44:11.240
what was it?
00:44:11.600 --> 00:44:14.620
You will end up having 18 threads running stuff.
00:44:14.780 --> 00:44:14.920
Yeah.
00:44:15.360 --> 00:44:18.480
Which is kind of what we want in general, right?
00:44:19.300 --> 00:44:20.840
Like if I write a program-
00:44:20.840 --> 00:44:22.140
I'm sure you can configure it, right?
00:44:22.220 --> 00:44:24.140
You could configure the thread pool to say,
00:44:24.220 --> 00:44:26.580
like, look, this thing has to be a good citizen.
00:44:26.820 --> 00:44:28.220
Let's just take the number of CPUs,
00:44:28.320 --> 00:44:29.400
divide by two or something like that
00:44:29.400 --> 00:44:30.460
so I can still do other work.
00:44:30.680 --> 00:44:33.060
Yeah, you can like, again,
00:44:34.080 --> 00:44:36.700
that Tonyo main decorator
00:44:36.980 --> 00:44:38.360
or the Tonyo run method
00:44:40.840 --> 00:44:42.080
accepts some parameters
00:44:42.220 --> 00:44:43.680
so you can configure like the size
00:44:43.700 --> 00:44:45.220
of the standard thread pool,
00:44:45.640 --> 00:44:47.640
the maximum amount of blocking threads
00:44:47.840 --> 00:44:48.680
you want to spawn
00:44:49.280 --> 00:44:51.020
because that does get spawned on demand.
00:44:52.460 --> 00:44:58.660
you can configure if you want to have support for context bars because it's not that's another thing
00:44:58.940 --> 00:45:07.360
about python right like we kind of went from thread locals to context bars uh but in thonio
00:45:07.520 --> 00:45:12.840
that's a bit more complicated right because you have async code in multiple threads yeah because
00:45:12.930 --> 00:45:18.340
they used to leverage the thread right yeah so if you want to actually use context bars
00:45:18.360 --> 00:45:20.900
you have to tell to the runtime, right?
00:45:20.980 --> 00:45:24.360
Because otherwise, like, some side effects might be weird
00:45:24.940 --> 00:45:25.560
in that condition.
00:45:26.620 --> 00:45:26.960
Hold on.
00:45:28.319 --> 00:45:30.240
Nomenclature definition, please, here.
00:45:30.500 --> 00:45:32.160
What are context vars for people who don't know?
00:45:32.260 --> 00:45:32.720
Give us examples.
00:45:32.840 --> 00:45:33.140
Oh, yeah.
00:45:33.540 --> 00:45:37.940
So context vars, so the interpreter has these,
00:45:39.060 --> 00:45:43.280
every thread into the Python interpreter
00:45:43.580 --> 00:45:45.780
has what it's called a context.
00:45:47.140 --> 00:45:51.160
And so with a sync.io, so what was the problem?
00:45:51.500 --> 00:46:01.280
So we used to have thread locals, which meant like if I have two threads and I have, for example, a request,
00:46:01.660 --> 00:46:10.900
and I have to keep the request state global, like global between quotes in my code,
00:46:11.100 --> 00:46:16.900
but I want to use the correct request in the code
00:46:17.810 --> 00:46:21.480
and don't make them mix in the two different threads, states,
00:46:22.460 --> 00:46:26.800
we use thread vocals because that's basically like a dictionary,
00:46:27.210 --> 00:46:30.260
more or less, where you can store that data.
00:46:30.640 --> 00:46:34.200
And that snapshot of data is for a single thread.
00:46:35.060 --> 00:46:39.200
Context var are a similar concept, but for a same code,
00:46:39.340 --> 00:46:42.480
which means you can store global,
00:46:43.440 --> 00:46:44.560
again, between quotes,
00:46:44.900 --> 00:46:50.280
global state from an asynchronous code,
00:46:50.880 --> 00:46:52.880
suspend, and when you get back,
00:46:53.360 --> 00:46:55.780
you have the correct object back
00:46:56.620 --> 00:46:58.600
instead of mixing global state
00:46:58.880 --> 00:46:59.840
between different coroutines.
00:47:00.040 --> 00:47:01.420
Was my explanation good?
00:47:01.760 --> 00:47:02.280
Yeah, that's good.
00:47:02.900 --> 00:47:04.200
People are probably pretty common
00:47:04.380 --> 00:47:05.680
with flask.request.
00:47:05.860 --> 00:47:06.080
Yeah.
00:47:06.120 --> 00:47:07.940
Because you've got a view method
00:47:07.960 --> 00:47:10.500
or maybe deep down inside some lower part of your program,
00:47:10.720 --> 00:47:12.300
you're like, well, I need to know what the URL was.
00:47:12.300 --> 00:47:14.260
You just say Flask.request, you don't pass it around.
00:47:14.460 --> 00:47:17.080
Like very handy, probably architecturally a bad choice.
00:47:17.960 --> 00:47:20.080
You know, it's hard to like test it out, right?
00:47:20.200 --> 00:47:20.700
And you mock it.
00:47:20.720 --> 00:47:21.140
I don't know.
00:47:21.640 --> 00:47:22.660
Emmet does the same.
00:47:23.860 --> 00:47:25.900
It's the one thing I copied from Flask
00:47:26.200 --> 00:47:27.100
because I really like it.
00:47:27.960 --> 00:47:30.880
But I think we can do the same argument about database, right?
00:47:31.060 --> 00:47:32.980
Like with a synchronous database,
00:47:33.140 --> 00:47:35.340
like you have to start a context,
00:47:35.740 --> 00:47:40.860
Like, async with database, whatever, and then you need to pass that, like, all through.
00:47:41.340 --> 00:47:43.440
Like, a context bar is more handy.
00:47:43.840 --> 00:47:44.260
I don't know.
00:47:44.960 --> 00:47:45.340
Yeah, yeah.
00:47:45.380 --> 00:47:47.880
It certainly unlocks some really interesting things.
00:47:48.020 --> 00:47:50.080
Some extensions do cool things with it.
00:47:50.340 --> 00:47:51.300
So that's what you're talking about.
00:47:51.440 --> 00:47:54.960
But because it's not all just tied to the thread and using asyncio context variables,
00:47:55.220 --> 00:47:57.260
now it's kind of shared in potential ways.
00:47:57.540 --> 00:47:59.840
It's a little bit, you got to opt into it.
00:47:59.940 --> 00:48:00.200
That's right?
00:48:00.440 --> 00:48:01.120
Yeah, CSR.
00:48:01.340 --> 00:48:03.560
Again, like, it's a Boolean when you want to start the runtime.
00:48:05.100 --> 00:48:08.740
And I mean, it's an implementation detail.
00:48:09.860 --> 00:48:11.380
So it is just to be like,
00:48:11.700 --> 00:48:13.780
I think the big warning at the beginning
00:48:14.400 --> 00:48:15.040
should have been like,
00:48:15.300 --> 00:48:16.580
Tonyo is very alpha right now.
00:48:18.000 --> 00:48:20.520
So maybe that decision will change in the future
00:48:20.680 --> 00:48:22.820
because I think right now the default is false.
00:48:23.540 --> 00:48:24.940
That might become true
00:48:25.410 --> 00:48:27.580
as soon as I stabilize the API.
00:48:28.420 --> 00:48:29.480
But yeah, the idea was
00:48:29.880 --> 00:48:32.080
if you're testing Tonyo right now,
00:48:32.560 --> 00:48:34.540
if you're using Tonyo to do some tests right now,
00:48:34.880 --> 00:48:41.560
I want the developer to explicitly state, okay, I'm going to use context bars.
00:48:41.960 --> 00:48:48.280
Because again, the fact you have multiple threads and stuff happening in parallel,
00:48:49.200 --> 00:48:55.300
I just want for the developer to be sure if it's thinking model is correct
00:48:55.460 --> 00:48:57.820
before trying to do some stuff.
00:48:59.160 --> 00:49:01.060
Because again, Tony is multi-threaded.
00:49:01.160 --> 00:49:07.900
multi-threaders is usually like not it's it's i don't know i think for humans multi-threading is
00:49:08.200 --> 00:49:16.520
hard to get sometimes um so that's the trade-off right like uh yes we now have all of these
00:49:16.700 --> 00:49:22.480
capabilities on the other end we need to think about the fact that the sync io always had this
00:49:22.940 --> 00:49:30.420
uh hidden uh let's say a feature which is hey there's only one coroutine running at the time
00:49:30.620 --> 00:49:32.640
Whereas Antonio can have like whatever number.
00:49:32.940 --> 00:49:33.100
Yep.
00:49:34.160 --> 00:49:37.160
Now you still had to manage stuff across await calls.
00:49:37.760 --> 00:49:43.520
So that was, I feel like people felt like the GIL saved them from all thread considerations,
00:49:43.960 --> 00:49:46.200
like locks and semaphores and so on.
00:49:46.460 --> 00:49:47.360
And I don't think it did.
00:49:47.640 --> 00:49:50.780
There's not a guarantee that like a thread couldn't interrupt you.
00:49:50.880 --> 00:49:52.780
It's just less likely to interrupt you, right?
00:49:53.080 --> 00:49:59.600
So like the simple thing is that it did around other primitives.
00:49:59.940 --> 00:50:08.680
So for example, in a SyncIO, it's really hard to deadlock yourself because you're using like a threading lock.
00:50:09.000 --> 00:50:16.000
Because again, like the hidden feature of a SyncIO is you have only one thread working on that shit, right?
00:50:16.740 --> 00:50:17.320
Yeah, exactly.
00:50:17.740 --> 00:50:19.080
Like you can do all the locks you want.
00:50:19.160 --> 00:50:20.600
It's the same thread in the re-entrance.
00:50:21.140 --> 00:50:21.380
Exactly.
00:50:21.920 --> 00:50:24.500
It's effectively a no-op other than it costs CPU.
00:50:24.920 --> 00:50:25.640
Yes, exactly.
00:50:25.860 --> 00:50:38.300
Whereas in Tonio, like that's exactly where people usually make mistakes because now you have the Tonio locking because you have the asynchronous locks, but you also have thread locks, which are two different things.
00:50:39.000 --> 00:50:42.800
And if you, and so like you can deadlock Tonio.
00:50:43.100 --> 00:50:43.300
How?
00:50:43.580 --> 00:50:43.700
Yeah.
00:50:44.020 --> 00:50:44.940
Welcome to multi-threading.
00:50:45.280 --> 00:50:45.480
Yes.
00:50:45.660 --> 00:50:52.160
Like you, you, you, you create, like you enter a threading lock and then you await a coroutine inside the threading lock.
00:50:52.310 --> 00:50:53.320
Now you're deadlocked.
00:50:53.580 --> 00:50:53.680
Yeah.
00:50:53.780 --> 00:50:55.860
Because the two things doesn't speak each other.
00:50:56.960 --> 00:50:59.700
But I guess my advice,
00:51:00.060 --> 00:51:03.360
if people want to start working in multi-threaded Python,
00:51:03.540 --> 00:51:08.620
my advice is just think about those two rules
00:51:09.140 --> 00:51:10.860
and then multi-threading is not that hard.
00:51:11.080 --> 00:51:12.380
Again, rule number one,
00:51:13.060 --> 00:51:16.800
never await inside a threading lock.
00:51:17.180 --> 00:51:17.960
That's a simple rule.
00:51:18.660 --> 00:51:21.740
Also because in Python, you use width lock.
00:51:22.240 --> 00:51:25.400
So that's super easy even to identify, right?
00:51:26.340 --> 00:51:27.620
That's rule number one.
00:51:28.120 --> 00:51:30.160
Rule number two, you have multiple threads.
00:51:30.510 --> 00:51:32.700
So if you want to write into anything,
00:51:33.760 --> 00:51:37.220
I mean, all the objects are thread safe in free thread of Python
00:51:37.420 --> 00:51:40.560
because a dictionary is thread safe, a list is thread safe, whatever.
00:51:41.080 --> 00:51:45.840
But that doesn't mean like you can screw up in using those.
00:51:46.020 --> 00:51:48.780
Because if you write state anywhere,
00:51:49.400 --> 00:51:58.560
You might have like race conditions and side effects because you expect to write something and then expect to read that value.
00:51:58.800 --> 00:52:01.380
But in the meantime, you have another thread right into the same location.
00:52:01.680 --> 00:52:08.180
So again, like my advice is rule number one, never use locks, threading locks, and then await.
00:52:08.580 --> 00:52:12.560
If you want to have an async lock, then use an async lock.
00:52:12.560 --> 00:52:13.880
So a Tonio lock for that.
00:52:14.720 --> 00:52:16.620
Rule number two, watch out.
00:52:16.820 --> 00:52:25.340
So you probably need locks when you want to be sure that, like, when you have a sequence of operation, right, on shared memory.
00:52:25.740 --> 00:52:26.880
Those are the two rules.
00:52:27.720 --> 00:52:31.740
I think with those two rules, multithreading is not that hard.
00:52:31.960 --> 00:52:32.440
Yeah, agreed.
00:52:32.660 --> 00:52:38.560
So does Tonio come with its own dedicated lock and synchronization primitives?
00:52:38.900 --> 00:52:41.460
Yes, there's an entire sync module.
00:52:41.980 --> 00:52:45.600
So Tonio has different modules.
00:52:46.580 --> 00:52:51.920
Tonya time, which contains time primitives, like timeouts, timers, etc.
00:52:52.300 --> 00:52:56.400
It has async module, which contains all the synchronization primitives,
00:52:56.580 --> 00:52:59.520
so logs, semaphore, barriers, etc.
00:53:01.000 --> 00:53:06.380
It has the network module, which is one providing like sockets.
00:53:06.960 --> 00:53:10.580
So that's quite a big difference from a sync.io.
00:53:10.580 --> 00:53:16.080
So in a sync.io, we don't have like an async socket library.
00:53:16.440 --> 00:53:20.620
So if you think about the standard lib socket module,
00:53:21.359 --> 00:53:25.480
Tonio, AsyncIO needs you to use that
00:53:25.760 --> 00:53:28.280
and then pass the sockets to the event loop
00:53:28.560 --> 00:53:30.320
and then handle the socket through the event.
00:53:31.200 --> 00:53:34.360
Whereas in Tonio, there's a network module
00:53:34.540 --> 00:53:36.340
and inside that module, there's a socket module,
00:53:36.670 --> 00:53:40.380
which has the same exact interface of the standard lib plus socket.
00:53:40.960 --> 00:53:42.040
The only difference is, of course,
00:53:42.600 --> 00:53:45.500
every async method is async in Tonio.
00:53:47.280 --> 00:53:47.640
there's the
00:53:48.860 --> 00:53:50.140
streams module
00:53:50.240 --> 00:53:52.140
inside the network module which is the high level
00:53:53.120 --> 00:53:53.900
trio like
00:53:54.580 --> 00:53:55.740
interface to
00:53:56.160 --> 00:53:57.440
make it easy to manage
00:53:57.980 --> 00:54:00.020
network. It has a file
00:54:00.140 --> 00:54:02.020
system module which
00:54:02.280 --> 00:54:02.640
contains
00:54:03.980 --> 00:54:04.640
all the standard
00:54:06.440 --> 00:54:07.880
open kind of
00:54:08.220 --> 00:54:09.060
methods that they
00:54:09.240 --> 00:54:10.300
async with open
00:54:10.860 --> 00:54:12.940
tonio.files.open something like that
00:54:13.100 --> 00:54:14.539
yeah.fs yes
00:54:15.260 --> 00:54:16.500
and it also contains like
00:54:17.400 --> 00:54:18.720
mirror of the
00:54:19.080 --> 00:54:20.260
pathlib, standard lib
00:54:20.680 --> 00:54:22.100
pathlib, so you can use like
00:54:22.400 --> 00:54:24.780
frontoniofs import path and that path
00:54:24.940 --> 00:54:26.060
has all the methods
00:54:26.700 --> 00:54:28.280
that are required to be asynchronous
00:54:29.840 --> 00:54:30.860
so that's
00:54:31.020 --> 00:54:32.120
more or less like the
00:54:33.000 --> 00:54:33.640
design
00:54:34.860 --> 00:54:36.260
I don't think like
00:54:36.420 --> 00:54:38.200
so right now I'm at
00:54:38.360 --> 00:54:40.680
tonio 0.9.14
00:54:40.960 --> 00:54:41.440
I guess
00:54:42.580 --> 00:54:44.520
and I don't think
00:54:44.540 --> 00:54:48.660
There's much left in terms of modules and primitives and features.
00:54:50.440 --> 00:54:52.280
The big feature missing is Windows support.
00:54:52.740 --> 00:54:53.440
More on that later.
00:54:55.839 --> 00:55:02.020
But yeah, I'd say I'm close to stabilize the API.
00:55:02.380 --> 00:55:04.440
So right now, again, this is all alpha.
00:55:05.040 --> 00:55:07.780
That's mostly because there's only me working on this.
00:55:08.920 --> 00:55:12.300
And I have Fable working on a bunch of other things,
00:55:12.920 --> 00:55:16.480
which I guess like if I can digress for a second,
00:55:17.040 --> 00:55:20.420
I think like I have a very peculiar and unique way of using AI
00:55:20.840 --> 00:55:22.020
because right now I'm like,
00:55:22.780 --> 00:55:26.080
and I guess like it's a good use case to advertise.
00:55:26.420 --> 00:55:28.180
So the entirety of Tonio,
00:55:28.340 --> 00:55:32.160
so Tonio has maybe 1% AI written code.
00:55:32.380 --> 00:55:35.900
The only code that was written by Claude
00:55:36.780 --> 00:55:38.140
was the pytest plugin.
00:55:38.540 --> 00:55:40.520
Oh, by the way, we also have a pytest plugin.
00:55:40.780 --> 00:55:42.760
so you can hide the smart tone.
00:55:45.200 --> 00:55:48.220
But that's the only part that I let AI touch.
00:55:49.640 --> 00:55:51.800
Everything else, like it's old school.
00:55:52.210 --> 00:55:56.780
I written all of it like the old way, text editor,
00:55:57.440 --> 00:56:01.280
not even any suggestion and everything.
00:56:02.420 --> 00:56:07.260
What I used AI for, and it helped me a lot
00:56:07.520 --> 00:56:12.660
into reaching a state in which I think Tonio is really stable now
00:56:13.520 --> 00:56:18.480
was to use AI to build projects that were using Tonio
00:56:18.860 --> 00:56:21.440
and stress test everything about Tonio.
00:56:21.510 --> 00:56:24.400
So for example, I made a port of Pi,
00:56:25.570 --> 00:56:27.520
the harness from Maria Zeckner.
00:56:28.510 --> 00:56:31.440
So I made Claude rewrite the whole thing in Python
00:56:32.200 --> 00:56:35.900
because Pi is written in TypeScript, regardless of the name.
00:56:37.320 --> 00:56:40.960
So I made that entire harness with Claude saying,
00:56:41.030 --> 00:56:42.020
okay, take five,
00:56:42.660 --> 00:56:46.380
meter every behavior and just use Tonio to do everything.
00:56:47.430 --> 00:56:49.200
And that like helped me like catch
00:56:49.510 --> 00:56:52.740
like probably 95% of the bugs in Tonio.
00:56:53.720 --> 00:56:55.920
And I kind of started doing the same for the ecosystem.
00:56:57.100 --> 00:56:59.320
Maybe we can talk about it a bit later,
00:56:59.740 --> 00:57:01.760
but yeah, that's also like a super,
00:57:02.480 --> 00:57:03.940
it's super useful for me, right?
00:57:04.080 --> 00:57:06.240
Like as a solo open source developer,
00:57:06.620 --> 00:57:08.660
which, yeah, I want to focus on Tonio.
00:57:09.080 --> 00:57:11.500
I don't have time to do all the other packages.
00:57:12.040 --> 00:57:13.320
And so, yeah, that was nice.
00:57:13.440 --> 00:57:14.760
That's a very interesting way.
00:57:14.760 --> 00:57:16.080
I hadn't really considered that.
00:57:16.300 --> 00:57:19.360
It's just like, I need users before I have users.
00:57:19.840 --> 00:57:21.840
I need these three use cases covered.
00:57:21.980 --> 00:57:24.240
So give me a web app that uses an async database.
00:57:24.500 --> 00:57:27.180
Give me this terminal app and so on.
00:57:27.180 --> 00:57:28.360
And that's, yeah, that's really cool.
00:57:28.720 --> 00:57:28.920
Yeah.
00:57:29.660 --> 00:57:29.800
Okay.
00:57:30.180 --> 00:57:30.620
I agree.
00:57:30.820 --> 00:57:31.700
Ecosystem is interesting.
00:57:31.780 --> 00:57:32.660
I want to talk about it.
00:57:32.840 --> 00:57:34.940
But before that, I want to come back to blocking threads.
00:57:35.420 --> 00:57:35.680
Yes.
00:57:35.960 --> 00:57:39.600
So blogging threads are CPU bound generally type of things.
00:57:39.610 --> 00:57:40.060
Is that right?
00:57:40.580 --> 00:57:44.680
So I think Tonio is a bit peculiar in this regard
00:57:45.120 --> 00:57:47.680
because it really depends what your program does.
00:57:48.160 --> 00:57:54.020
Because if your program, whatever that is, has mixed load,
00:57:54.210 --> 00:57:58.180
which means like you have both IO bound stuff,
00:57:58.700 --> 00:58:03.960
like network, disk, terminal, whatever, and CPU bound stuff,
00:58:05.180 --> 00:58:10.460
then the only way to be sure that everything keeps running smoothly,
00:58:10.820 --> 00:58:13.300
like on the network part, the IO part,
00:58:13.620 --> 00:58:18.840
is to spawn the CPU bound stuff on the blocking thread pool.
00:58:19.460 --> 00:58:24.740
But if your program is just CPU bound, then who cares?
00:58:24.990 --> 00:58:28.580
Just use the normal thread pool
00:58:29.710 --> 00:58:31.700
because then you don't have anything to block.
00:58:32.000 --> 00:58:34.360
like your CPU bound, your CPU limited.
00:58:35.280 --> 00:58:37.340
So the standard configuration of Tonio
00:58:37.800 --> 00:58:40.760
gives you exactly the perfect number of CPU cores to use.
00:58:41.860 --> 00:58:45.520
On the other hand, if you just have like IO bound.
00:58:46.100 --> 00:58:48.920
So again, like if you have just CPU bound work
00:58:49.100 --> 00:58:50.440
or just IO bound work,
00:58:50.500 --> 00:58:52.540
you can use like the standard pool,
00:58:53.860 --> 00:58:55.200
nothing particular like this.
00:58:55.440 --> 00:58:57.140
The blocking thread pool is useful
00:58:57.440 --> 00:58:59.200
only when you have mixed workloads
00:58:59.820 --> 00:59:01.700
and you need to balance them, right?
00:59:01.960 --> 00:59:03.820
Okay, how do I create such a thing?
00:59:04.160 --> 00:59:07.440
How do I start a task in one or the other?
00:59:07.980 --> 00:59:11.060
So every await or yell destruction
00:59:11.700 --> 00:59:12.780
or a tonio spawn destruction
00:59:13.480 --> 00:59:15.600
runs on the standard thread pool.
00:59:16.660 --> 00:59:19.280
To spawn something on the blocking thread pool
00:59:19.440 --> 00:59:20.860
is just tonio spawn blocking.
00:59:21.190 --> 00:59:21.300
Done.
00:59:21.670 --> 00:59:23.640
You don't need to manage the size of the pool.
00:59:23.690 --> 00:59:26.140
You don't need to create the pool.
00:59:27.480 --> 00:59:32.700
If you want to control concurrency in the blocking threat pool,
00:59:34.240 --> 00:59:37.200
you either use, like, barriers when you spawn stuff
00:59:38.320 --> 00:59:40.260
or semaphores when you spawn stuff.
00:59:41.700 --> 00:59:42.820
But, yeah, like, in general,
00:59:43.300 --> 00:59:45.740
it's not different from spawning anything else.
00:59:46.160 --> 00:59:46.340
Sure.
00:59:46.620 --> 00:59:47.640
So you can spawn blocking.
00:59:48.540 --> 00:59:49.640
Here's what I was thinking.
00:59:49.800 --> 00:59:50.620
Here's what I was getting at.
00:59:51.440 --> 00:59:53.540
That I think would be a sweet feature.
00:59:53.980 --> 00:59:57.100
You've got at tonio.main for the entry point, right?
00:59:57.560 --> 00:59:57.700
Yes.
00:59:58.260 --> 01:00:01.640
So that runs on tonio.run instead of just Python run or whatever.
01:00:02.000 --> 01:00:05.200
For functions that you know are computational,
01:00:05.440 --> 01:00:08.140
you would like to ensure that they run on a blocking thread
01:00:08.640 --> 01:00:09.820
or along those lines.
01:00:10.120 --> 01:00:15.540
You could put a decorator that just says tonio.cpu or whatever.
01:00:15.640 --> 01:00:16.000
I don't know.
01:00:16.120 --> 01:00:19.000
You come up with a name, but that way I don't even have to think.
01:00:19.400 --> 01:00:21.840
If I import another library also built on tonio,
01:00:22.040 --> 01:00:25.020
I don't know, oh, that function wants me to run it this way.
01:00:25.490 --> 01:00:26.480
No, I just call it.
01:00:26.640 --> 01:00:29.600
Antonio goes, ah, this is an async CPU one.
01:00:29.910 --> 01:00:33.680
So we'll scale it up, scaffold it up on that, basically.
01:00:33.960 --> 01:00:36.200
That's a nice suggestion, okay?
01:00:36.940 --> 01:00:37.200
All right.
01:00:37.320 --> 01:00:40.200
I think it will land in Antonio 0.10, yes.
01:00:40.580 --> 01:00:41.300
Awesome, okay, cool.
01:00:41.680 --> 01:00:42.380
I like that.
01:00:43.260 --> 01:00:46.720
When you're in the function, the locality of the information
01:00:46.790 --> 01:00:49.080
that, oh, this is computational and so on is right there.
01:00:49.170 --> 01:00:50.920
But when you're calling, and especially in a big app,
01:00:51.090 --> 01:00:51.700
you don't know, right?
01:00:51.780 --> 01:00:52.680
So I think that would be really cool.
01:00:53.060 --> 01:00:53.880
So I'm glad you like it.
01:00:54.060 --> 01:00:55.080
No, we'll work on that.
01:00:55.640 --> 01:00:55.940
Awesome.
01:00:56.940 --> 01:00:58.620
All right, we got time for two really quick things.
01:00:59.040 --> 01:01:02.100
So one thing that is nice about standard asyncio
01:01:02.280 --> 01:01:03.640
is everyone uses asyncio
01:01:03.840 --> 01:01:05.460
if they're writing async code at the moment,
01:01:05.930 --> 01:01:06.840
unless they do something like Trio
01:01:06.970 --> 01:01:07.680
or one of these other things.
01:01:08.060 --> 01:01:11.760
So for example, if I use HTTPX2 or HTTPX
01:01:12.300 --> 01:01:14.520
and I, with async, you know,
01:01:14.840 --> 01:01:16.440
async with create a client of it
01:01:16.500 --> 01:01:17.520
and I await its get,
01:01:18.260 --> 01:01:20.180
that runs on the same sort of deal.
01:01:20.440 --> 01:01:23.040
but it's probably not compatible with Tonio, is it?
01:01:23.270 --> 01:01:24.340
And if it's not, what do I do?
01:01:24.940 --> 01:01:25.760
Yeah, it's not.
01:01:28.580 --> 01:01:32.080
So I have one thing that is the current state,
01:01:32.380 --> 01:01:35.580
and I also have the idea for the future.
01:01:35.850 --> 01:01:40.060
So right now we have a package which is called TonioMonkey,
01:01:40.460 --> 01:01:43.140
and you probably can guess what it means, what it does.
01:01:43.820 --> 01:01:48.180
It basically like monkey patches popular,
01:01:48.860 --> 01:01:55.520
Or I would either say, like, asyncio native libraries I thought about in the last few months.
01:01:56.580 --> 01:01:57.480
That might be useful.
01:01:59.320 --> 01:02:04.980
And so I guess today we have, like, TonyaMonkey can monkey patch async.
01:02:05.320 --> 01:02:05.860
No, sorry.
01:02:06.640 --> 01:02:07.200
PsychoPG.
01:02:08.380 --> 01:02:11.200
It can patch HTTPX and HTTPX2.
01:02:12.200 --> 01:02:13.760
It can patch Redis.
01:02:15.220 --> 01:02:17.240
And I think that's most of it for the moment.
01:02:17.760 --> 01:02:21.040
But I'm open for issues.
01:02:21.360 --> 01:02:28.440
If anyone from the listeners want to try Tonio to an existing code base
01:02:28.680 --> 01:02:32.640
and you have a package you would like to see monkey patched,
01:02:33.180 --> 01:02:35.340
just open an issue in the TonioMonkey repo
01:02:35.900 --> 01:02:39.900
or even in Tonio discussions, and I will look at it.
01:02:41.560 --> 01:02:46.860
Because again, right now I need the vast majority of possible use cases
01:02:46.880 --> 01:02:48.480
to see that I covered everything.
01:02:50.140 --> 01:02:52.460
Ideally, as soon as I stabilize the API
01:02:52.680 --> 01:02:55.040
and I say, okay, this is the thing,
01:02:56.200 --> 01:03:03.380
my idea was to try to contact the NEIO maintainers
01:03:04.900 --> 01:03:13.260
to see if we can have NEIO backend in NEIO,
01:03:13.680 --> 01:03:19.820
which should be like a way easier way to deal with this.
01:03:20.900 --> 01:03:22.720
I'm not sure how much it's feasible because again,
01:03:22.900 --> 01:03:27.540
like the main problem for Tonio right now is that it doesn't support any of the
01:03:27.740 --> 01:03:28.600
synchio primitives.
01:03:28.880 --> 01:03:33.260
So if any code base like relies a ton on task or futures or whatever,
01:03:34.660 --> 01:03:36.520
that's not in the runtime.
01:03:36.940 --> 01:03:37.060
Yeah.
01:03:37.300 --> 01:03:37.420
Yeah.
01:03:37.760 --> 01:03:37.880
Okay.
01:03:38.400 --> 01:03:38.640
Interesting.
01:03:38.980 --> 01:03:39.140
Interesting.
01:03:39.340 --> 01:03:40.560
But there are some choices.
01:03:40.820 --> 01:03:42.580
And are you also creating your own?
01:03:42.740 --> 01:03:48.320
I saw some custom HTTP or some other library or two.
01:03:48.980 --> 01:03:53.640
I'm also trying to be a very contained ecosystem of libraries,
01:03:54.120 --> 01:03:55.460
mainly on the web part.
01:03:55.660 --> 01:04:01.580
So right now, I published a low-level HTTP library for Python
01:04:02.430 --> 01:04:06.440
that is compatible both with Asyncio and Tonio.
01:04:07.040 --> 01:04:08.680
It's called HTTPunk.
01:04:09.100 --> 01:04:17.740
I published an eye-level client, HTTP client, on top of that, which is called Punk Rec.
01:04:19.360 --> 01:04:27.020
So again, you can use that regardless of Tonio Asincaio, because the adaptation layer is inside the package.
01:04:27.580 --> 01:04:38.340
I kind of published a very beta experiment of a Uvicorn port to work in Tonio,
01:04:38.680 --> 01:04:45.120
Which I guess brings up a question like, hey, when will Granian support this?
01:04:45.430 --> 01:04:45.960
Am I right?
01:04:46.220 --> 01:04:46.560
Yeah, right.
01:04:47.640 --> 01:04:48.720
So I have plans.
01:04:50.080 --> 01:04:52.700
It's actually way bigger plans for Granian.
01:04:53.820 --> 01:04:58.700
So as I said before, 2026 was a bit boring in Granian.
01:04:58.900 --> 01:05:02.340
Like, no, a bunch of new features, not a bunch of that stuff.
01:05:02.390 --> 01:05:08.140
But that's just because several months ago, I started designing two things for Granian.
01:05:08.280 --> 01:05:13.140
So the first is this revision two of the ArchGee protocol,
01:05:13.680 --> 01:05:17.020
which will be async independent.
01:05:17.600 --> 01:05:20.580
So it will become like a callback protocol
01:05:21.780 --> 01:05:25.720
because the other big feature I want to add in Granian
01:05:26.180 --> 01:05:29.320
is to support a bunch of runtimes.
01:05:29.660 --> 01:05:31.620
So my idea is not the Granian tree,
01:05:32.240 --> 01:05:36.040
when it will land, no promises on the time here,
01:05:36.380 --> 01:05:37.640
but like when it will land,
01:05:37.980 --> 01:05:45.700
Ideally, it will support Asyncio, Trio, Tonio, gEvent, eventlet, and a bunch of stuff.
01:05:45.890 --> 01:05:55.360
So that's the idea, is to isolate, let's say, the protocol implementation and the Python runtime implementation.
01:05:55.620 --> 01:05:57.540
So it will happen one day.
01:05:59.660 --> 01:06:04.620
In the meantime, I wanted to have a test server to use with Tonio and see what happens.
01:06:04.890 --> 01:06:05.640
So that's why.
01:06:07.220 --> 01:06:08.420
Yeah, using something like
01:06:08.780 --> 01:06:10.860
Cord or FastAPI or something like that.
01:06:11.120 --> 01:06:12.900
If you were able to run it in Granian
01:06:13.400 --> 01:06:15.020
with the Tonio, Tonio
01:06:15.520 --> 01:06:17.220
backend, the async
01:06:17.560 --> 01:06:18.700
endpoints you write would be
01:06:19.120 --> 01:06:19.960
running on Tonio, yeah?
01:06:20.240 --> 01:06:21.360
Yeah, that's the plan.
01:06:22.800 --> 01:06:25.340
In TonioMonkey, there are also patches for FastAPI,
01:06:25.480 --> 01:06:26.820
by the way. And I'm working
01:06:27.060 --> 01:06:28.480
on a one on Django.
01:06:28.920 --> 01:06:30.760
I need to ping
01:06:30.900 --> 01:06:31.900
Carleton on that
01:06:32.820 --> 01:06:34.380
because we met this year
01:06:34.420 --> 01:06:36.400
at this year, PyCon Italy,
01:06:36.940 --> 01:06:38.040
and we talk about it.
01:06:39.259 --> 01:06:42.240
So maybe in a month or so,
01:06:42.660 --> 01:06:44.440
we will also have like a patch for Django.
01:06:44.720 --> 01:06:44.880
Sweet.
01:06:45.190 --> 01:06:51.360
Yeah, I just had Carlton on to talk about
01:06:52.260 --> 01:06:55.700
basically all the async work in Django 6 and 6.0 and so on.
01:06:55.730 --> 01:06:56.360
So that was fun.
01:06:56.400 --> 01:06:58.600
I watched the episode and I was like,
01:06:58.900 --> 01:07:01.420
huh, sounds familiar, huh, huh.
01:07:01.960 --> 01:07:02.980
Yeah, we've talked about that.
01:07:03.030 --> 01:07:03.380
I see.
01:07:03.820 --> 01:07:04.060
Interesting.
01:07:04.480 --> 01:07:04.660
All right.
01:07:04.720 --> 01:07:07.520
Well, I got one final main topic for us.
01:07:07.860 --> 01:07:08.020
Yep.
01:07:08.280 --> 01:07:08.680
Oh, yeah.
01:07:08.720 --> 01:07:10.200
What about windows?
01:07:12.260 --> 01:07:12.520
So.
01:07:12.800 --> 01:07:15.180
And I'm not putting pressure on you to do it.
01:07:15.260 --> 01:07:15.700
No, no, no.
01:07:16.060 --> 01:07:17.560
I'm just rounding out the conversation.
01:07:18.040 --> 01:07:18.300
Okay.
01:07:18.660 --> 01:07:26.480
So if you ask me like a few months ago, my answer would probably be like that short video
01:07:26.920 --> 01:07:31.740
extract from a series like where you had the actor.
01:07:32.780 --> 01:07:32.940
Whatever.
01:07:33.080 --> 01:07:41.700
um you know i'm gonna not gonna not gonna swear on the podcast but uh uh you get what i mean
01:07:41.800 --> 01:07:48.640
uh so i i'm not so first of all like the usual problem so supporting windows in granian
01:07:49.180 --> 01:07:55.220
it's a pain man like a real pain like i'm not joking right because it has like all these
01:07:55.560 --> 01:08:02.500
quirk behaviors not just in python not just in terms of threads like do you know that win that
01:08:02.520 --> 01:08:11.220
that windows have as like a bug from i think it was like nt4 uh that like if you if you share a
01:08:11.430 --> 01:08:17.859
socket it becomes blocking and so you cannot use it like anywhere um so windows for start like
01:08:18.029 --> 01:08:26.299
windows is a pain and is a very weird operating system second i don't have like a windows environment
01:08:27.440 --> 01:08:29.120
because finally in 2026,
01:08:29.380 --> 01:08:32.200
I was able to finally have just macOS and Linux
01:08:32.580 --> 01:08:34.980
on all my machines, also for gaming.
01:08:35.319 --> 01:08:36.580
So I'm so happy.
01:08:37.960 --> 01:08:39.420
So those are, like I say,
01:08:39.759 --> 01:08:43.240
the two main preambles on Windows support.
01:08:44.499 --> 01:08:48.359
But also because implementing the library
01:08:48.400 --> 01:08:52.120
I use Antonio to manage the Polar,
01:08:52.339 --> 01:08:53.819
the actual event loop,
01:08:54.859 --> 01:08:58.220
It supports Windows, but you know, weird way.
01:08:58.580 --> 01:09:03.520
So, months ago I was like almost a hundred percent convinced now,
01:09:03.839 --> 01:09:08.799
never windows, never now, thanks to fable by chatting with fable.
01:09:09.240 --> 01:09:18.380
I think we found a way to hack into, into meal, this library to, to fake
01:09:18.779 --> 01:09:20.140
some stuff about windows.
01:09:20.220 --> 01:09:24.359
So it will interpret some stuff as TCP sockets.
01:09:24.460 --> 01:09:27.540
even if they are like file descriptors or anything.
01:09:28.140 --> 01:09:30.980
So I won't make a promise here
01:09:31.380 --> 01:09:33.900
because then I have to do it.
01:09:34.240 --> 01:09:37.759
But ideally, end of the year,
01:09:38.120 --> 01:09:41.020
we might see partial Windows support in Tom.
01:09:41.190 --> 01:09:41.319
Sweet.
01:09:41.640 --> 01:09:43.420
So that's the idea right now.
01:09:44.120 --> 01:09:47.640
I still hate Microsoft, to be clear here.
01:09:47.640 --> 01:09:49.740
Like, man, I just like Windows.
01:09:51.720 --> 01:09:54.340
So if any of the listeners,
01:09:54.450 --> 01:09:56.860
like if you're programming in Windows
01:09:57.560 --> 01:09:58.740
and not using Weasel,
01:09:59.160 --> 01:10:00.720
please put down in the comments why.
01:10:01.220 --> 01:10:04.960
Like, why are you self-inflicting this to you?
01:10:05.140 --> 01:10:07.440
Like, explain me the rationale
01:10:08.100 --> 01:10:11.180
into dealing with all the BS
01:10:11.480 --> 01:10:13.200
that Windows 11 is nowadays.
01:10:14.620 --> 01:10:15.580
So yeah, but anyways,
01:10:17.080 --> 01:10:18.440
I will try end of the year
01:10:18.440 --> 01:10:21.120
to have some form of Windows support in time.
01:10:21.700 --> 01:10:23.480
I can tell, I can sense your excitement.
01:10:23.880 --> 01:10:25.800
My Windows computer over there is Windows 10, by the way.
01:10:26.220 --> 01:10:27.520
But I do think for people listening,
01:10:28.240 --> 01:10:30.600
Windows Subsystem for Linux is an escape hatch
01:10:30.950 --> 01:10:31.860
that you can do, right?
01:10:32.020 --> 01:10:33.920
If you had a project, you really want to use this here,
01:10:34.100 --> 01:10:35.840
like set up Windows Subsystem for Linux
01:10:35.950 --> 01:10:36.620
and run it there, right?
01:10:36.840 --> 01:10:40.320
To be here, like the only reason I want to,
01:10:40.500 --> 01:10:42.680
like I want to try to add Windows support in Tonio
01:10:42.820 --> 01:10:43.920
is not for developers,
01:10:44.420 --> 01:10:47.880
is actually to provide developers a way to say,
01:10:48.050 --> 01:10:50.300
okay, I want to build a program in Python
01:10:50.300 --> 01:10:53.180
with Tonio that has to work like everywhere.
01:10:53.700 --> 01:10:54.240
Because right now, again,
01:10:54.440 --> 01:10:56.280
like if you code anything in Tonio,
01:10:56.700 --> 01:10:58.480
it works only on POSIX systems,
01:10:58.640 --> 01:10:59.860
so only Linux and macOS.
01:11:00.280 --> 01:11:02.100
So that's the reason why I want to have
01:11:02.300 --> 01:11:04.360
at least some form of Windows support
01:11:04.480 --> 01:11:06.620
because, I mean, it's not about developers.
01:11:07.340 --> 01:11:09.080
It's about like where the software runs
01:11:09.400 --> 01:11:09.940
at the end of the day.
01:11:10.100 --> 01:11:10.520
Right, right.
01:11:10.620 --> 01:11:12.180
It's the deployment targets, yeah.
01:11:12.560 --> 01:11:12.640
Yeah.
01:11:13.040 --> 01:11:14.360
All right, Joe, final call to action.
01:11:14.620 --> 01:11:15.460
People have been listening
01:11:15.560 --> 01:11:17.000
and they're like, this sounds pretty excellent.
01:11:17.600 --> 01:11:18.380
I want to try it.
01:11:18.740 --> 01:11:19.300
What do you tell them?
01:11:19.540 --> 01:11:27.760
I mean, like, again, I think we're living in the perfect moment to try stuff.
01:11:28.780 --> 01:11:33.900
We've given with this big opportunity, big new opportunity of free-threaded Python.
01:11:35.140 --> 01:11:36.960
We have AI agents.
01:11:38.500 --> 01:11:44.040
And by the way, I was surprised, but like pointing any agent, any model,
01:11:44.780 --> 01:11:48.620
frontier model to a project and say, okay, implement this in Tonio.
01:11:48.840 --> 01:11:49.480
I was surprised.
01:11:49.620 --> 01:11:52.480
They got all the API correctly most of the times,
01:11:53.660 --> 01:11:54.220
which is amazing.
01:11:54.480 --> 01:11:56.580
There's no training data about Tonio.
01:11:56.940 --> 01:12:01.360
And regardless, it was able to drive through stuff.
01:12:02.440 --> 01:12:04.880
So I guess it's the perfect moment to try stuff.
01:12:06.820 --> 01:12:11.440
I'd say if for any reason you're not happy with Asyncio,
01:12:11.700 --> 01:12:18.420
if for any reason you experience any of the pain points we talked about,
01:12:18.740 --> 01:12:23.460
I think it's like the perfect moment to try new stuff.
01:12:24.040 --> 01:12:28.440
And if you try Tonio and you have like a use case
01:12:29.000 --> 01:12:33.040
where it's not working or the API can be improved,
01:12:33.280 --> 01:12:35.540
like open an issue, open a discussion,
01:12:36.260 --> 01:12:39.120
ping me, DM me on Twitter.
01:12:39.590 --> 01:12:41.360
Oh, sorry, X the everything up.
01:12:41.540 --> 01:12:42.420
I will call it.
01:12:44.440 --> 01:12:47.120
Ping me and we'll figure out.
01:12:48.660 --> 01:12:51.100
Yeah, I think that's my message.
01:12:51.860 --> 01:12:52.580
Yeah, awesome.
01:12:52.640 --> 01:12:54.520
Well, it looks like a very ambitious project
01:12:54.880 --> 01:12:56.900
and I think you've done a lot of interesting things.
01:12:57.080 --> 01:12:58.520
So thanks for coming on and sharing it.
01:12:58.740 --> 01:13:00.800
Tell people where they can stay in touch with you as well.
01:13:01.140 --> 01:13:08.700
Yes, so my GitHub handle is G-I-0-B-A-R-O.
01:13:09.500 --> 01:13:14.120
You can find me with the same handle
01:13:14.140 --> 01:13:23.960
Twitter. I have a blog, even if I don't write a ton, but you can find my blog at blog.baro.dev.
01:13:24.380 --> 01:13:35.900
And if you want to join me and Marcelo Trilesinski, again, author of Uvicorn, Starlet, and a bunch of
01:13:36.800 --> 01:13:42.700
stuff he's making with TokenMaxing, we recently started a podcast available on YouTube.
01:13:44.440 --> 01:13:46.620
at the www pod.
01:13:47.060 --> 01:13:47.160
Nice.
01:13:47.600 --> 01:13:49.000
So people, check them out.
01:13:49.140 --> 01:13:49.680
Check out the pod.
01:13:49.840 --> 01:13:50.260
That sounds fun.
01:13:50.340 --> 01:13:51.620
I'll give it a look as well.
01:13:51.780 --> 01:13:52.920
Joe, thanks for coming on the show.
01:13:53.160 --> 01:13:53.880
Nice to catch up with you.
01:13:54.120 --> 01:13:54.840
Thank you for having me.
01:13:55.140 --> 01:13:56.400
It was super good.
01:13:57.700 --> 01:13:59.840
This has been another episode of Talk Python To Me.
01:14:00.220 --> 01:14:00.940
Thank you to our sponsors.
01:14:01.240 --> 01:14:02.420
Be sure to check out what they're offering.
01:14:02.660 --> 01:14:04.000
It really helps support the show.
01:14:04.560 --> 01:14:07.060
This episode is sponsored by Sentry's Seer.
01:14:07.380 --> 01:14:09.800
If you're tired of debugging in the dark, give Seer a try.
01:14:10.360 --> 01:14:12.500
There are plenty of AI tools that help you write code,
01:14:12.720 --> 01:14:15.580
but Sentry's seer is built to help you fix it when it breaks.
01:14:16.340 --> 01:14:20.420
Visit talkpython.fm/sentry and use the code talkpython26,
01:14:20.880 --> 01:14:24.040
all one word, no spaces, for $100 in Sentry credits.
01:14:24.740 --> 01:14:26.360
And it's brought to you by us.
01:14:26.960 --> 01:14:30.080
Talk Python and Python Bytes both now have MCP servers.
01:14:30.660 --> 01:14:33.460
Point your AI at 10 plus years of Python episodes,
01:14:34.100 --> 01:14:35.600
transcripts, and show notes free.
01:14:36.020 --> 01:14:39.800
Click MCP in the nav at talkpython.fm and at Python Bytes.
01:14:40.200 --> 01:14:42.080
If you or your team needs to learn Python,
01:14:42.380 --> 01:14:48.160
We have over 270 hours of beginner and advanced courses on topics ranging from complete beginners
01:14:48.450 --> 01:14:52.300
to async code, Flask, Django, HTMX, and even LLMs.
01:14:52.660 --> 01:14:54.860
Best of all, there's no subscription in sight.
01:14:55.400 --> 01:14:57.180
Browse the catalog at talkpython.fm.
01:14:57.860 --> 01:15:01.980
And if you're not already subscribed to the show on your favorite podcast player, what
01:15:01.980 --> 01:15:02.540
are you waiting for?
01:15:03.220 --> 01:15:04.960
Just search for Python in your podcast player.
01:15:05.120 --> 01:15:05.940
We should be right at the top.
01:15:06.380 --> 01:15:09.140
If you enjoy that geeky rap song, you can download the full track.
01:15:09.350 --> 01:15:11.260
The link is actually in your podcast blur show notes.
01:15:12.040 --> 01:15:13.420
This is your host, Michael Kennedy.
01:15:13.820 --> 01:15:14.880
Thank you so much for listening.
01:15:15.050 --> 01:15:15.880
I really appreciate it.
01:15:16.300 --> 01:15:17.020
I'll see you next time.
01:15:27.480 --> 01:15:28.420
I'm me.
01:15:29.820 --> 01:15:30.920
Get me ready to roll.
01:15:32.560 --> 01:15:33.700
Upgrade the code.
01:15:34.560 --> 01:15:36.240
No fear of getting old.
01:15:37.740 --> 01:15:39.720
We tapped into that modern vibe
01:15:40.160 --> 01:15:41.140
over Kenny's storm.
01:16:11.040 --> 01:16:11.060
Продолжение следует...
00:00:00.020 --> 00:00:01.500
How many cores does your machine have?
00:00:02.080 --> 00:00:02.220
10?
00:00:02.720 --> 00:00:02.960
18?
00:00:03.740 --> 00:00:06.840
Well, your async Python code uses just one of them.
00:00:07.580 --> 00:00:09.060
That isn't a bug in asyncio.
00:00:09.420 --> 00:00:10.180
That's the design.
00:00:10.840 --> 00:00:16.940
And things like optimizing the event loops to be faster by maybe 20% with uv doesn't fundamentally
00:00:17.090 --> 00:00:17.480
change that.
00:00:17.760 --> 00:00:20.520
That's why Giovanni Bariliari started over.
00:00:21.180 --> 00:00:26.400
Joe is the creator of Granian, the Rust-based server that powers Talk Python and all the
00:00:26.450 --> 00:00:27.740
other things we run here.
00:00:28.020 --> 00:00:33.580
His new project is Tone.io, an async runtime written from scratch specifically for free-threaded Python.
00:00:34.280 --> 00:00:38.320
Real threads, a handful of primitives instead of async.io's pile of them,
00:00:38.820 --> 00:00:43.260
and it flat out refuses to even start if the GIL is present.
00:00:43.880 --> 00:00:49.600
This is Talk Python To Me, episode 561, recorded September 1st, 2026.
00:01:10.620 --> 00:01:14.940
Welcome to Talk Python To Me, the number one Python podcast for developers and data scientists.
00:01:15.460 --> 00:01:16.840
This is your host, Michael Kennedy.
00:01:17.220 --> 00:01:20.760
I'm a PSF fellow who's been coding for over 25 years.
00:01:21.400 --> 00:01:22.520
Let's connect on social media.
00:01:22.840 --> 00:01:26.000
You'll find me and Talk Python on Mastodon, BlueSky, and X.
00:01:26.190 --> 00:01:28.120
The social links are all in your show notes.
00:01:28.880 --> 00:01:32.400
You can find over 10 years of past episodes at talkpython.fm.
00:01:32.580 --> 00:01:35.780
And if you want to be part of the show, you can join our recording live streams.
00:01:36.120 --> 00:01:36.600
That's right.
00:01:36.790 --> 00:01:40.060
We live stream the raw uncut version of each episode on YouTube.
00:01:40.460 --> 00:01:45.060
Just visit talkpython.fm/youtube to see the schedule of upcoming events.
00:01:45.290 --> 00:01:48.940
Be sure to subscribe there and press the bell so you'll get notified anytime we're recording.
00:01:49.840 --> 00:01:52.380
This episode is sponsored by Sentry's Seer.
00:01:52.660 --> 00:01:55.020
If you're tired of debugging in the dark, give Seer a try.
00:01:55.680 --> 00:01:59.320
There are plenty of AI tools that help you write code, but Sentry's Seer is built to
00:01:59.320 --> 00:02:00.900
help you fix it when it breaks.
00:02:01.480 --> 00:02:07.200
Visit talkpython.fm/sentry and use the code Talk Python26, all one word, no spaces,
00:02:07.620 --> 00:02:09.340
for $100 in Sentry credits.
00:02:09.979 --> 00:02:11.720
And it's brought to you by us.
00:02:12.360 --> 00:02:15.440
Talk Python and Python Bytes both now have MCP servers.
00:02:16.040 --> 00:02:20.440
Point your AI at 10 plus years of Python episodes, transcripts, and show notes.
00:02:20.840 --> 00:02:25.160
Free. Click MCP in the nav at talkpython.fm and at Python Bytes.
00:02:25.760 --> 00:02:29.020
Hello, hello. Welcome back to Talk Python and me.
00:02:29.350 --> 00:02:30.020
Great to have you here, Joe.
00:02:30.280 --> 00:02:33.640
Hey, thanks for having me. It's nice to get back to the show.
00:02:33.930 --> 00:02:34.540
It's been a while.
00:02:35.040 --> 00:02:35.920
It has been a while.
00:02:36.220 --> 00:02:41.800
And we talked about Granian last time, the Rust-based web application server for Python.
00:02:42.200 --> 00:02:44.200
And I got to just take a moment and say thank you.
00:02:44.320 --> 00:02:45.000
We'll come back to this.
00:02:45.040 --> 00:02:49.400
But thanks for powering Talk Python and all the different things.
00:02:49.480 --> 00:02:52.680
I think across our web apps, I don't know, like 10 million requests a month.
00:02:52.780 --> 00:02:53.740
And it's been flawless.
00:02:54.920 --> 00:02:55.720
So thanks.
00:02:55.880 --> 00:02:58.420
I appreciate you serving up all the stuff for everyone.
00:02:58.680 --> 00:02:59.120
Oh, yeah.
00:02:59.340 --> 00:02:59.440
Thanks.
00:03:00.220 --> 00:03:00.820
It's been a pleasure.
00:03:00.980 --> 00:03:04.540
Like, it's nice to see, you know, when something just works.
00:03:04.980 --> 00:03:09.660
like when you do something and like at the end of the day it just works and people start using it
00:03:09.740 --> 00:03:16.560
like of course like they they ask for a bunch of features you know um but it's nice to see yeah it
00:03:16.580 --> 00:03:21.300
is really nice to see and i think it's i think it's good now it's been a while since you've been on the
00:03:21.400 --> 00:03:26.440
show a couple years i don't i didn't check the exact date when i look back maybe people are new
00:03:26.480 --> 00:03:30.419
to the show or haven't heard your prior episode or just don't remember like give us a quick
00:03:30.440 --> 00:03:37.160
introduction who you are yeah sure so i'm giovanni but everybody can call me joe i'm italian but i
00:03:37.160 --> 00:03:44.400
live in vienna i am working as a programmer in century but i'm a physicist so i think like
00:03:44.760 --> 00:03:53.360
that's pretty peculiar i've been like in the open source ecosystem uh mainly in python um probably
00:03:53.440 --> 00:04:00.560
for the last oh god I think like it's almost 20 years now um and fun fact like I actually started
00:04:00.800 --> 00:04:07.160
like um doing open source or or um contributing to open source projects even before actually
00:04:07.440 --> 00:04:16.900
working as a programmer so um yeah that's me I maintain like too many packages nowadays for like
00:04:16.959 --> 00:04:18.380
the energy I have.
00:04:20.080 --> 00:04:22.019
But yeah, that's pretty much.
00:04:22.360 --> 00:04:22.640
Excellent.
00:04:22.890 --> 00:04:25.320
Yeah, I know how that goes with the packages.
00:04:25.660 --> 00:04:26.740
It's like, oh, here's a cool idea.
00:04:26.740 --> 00:04:27.500
I want to put it out there.
00:04:27.800 --> 00:04:29.520
If it gains no traction, you can shut it down.
00:04:30.080 --> 00:04:32.000
If it gains a ton of traction, you're excited.
00:04:32.100 --> 00:04:33.280
If it gains a little traction,
00:04:33.780 --> 00:04:35.140
but enough, then you got to keep working on it.
00:04:35.200 --> 00:04:36.740
That's kind of not ideal, right?
00:04:37.080 --> 00:04:37.320
Yeah.
00:04:37.430 --> 00:04:39.060
And especially like nowadays with AI,
00:04:39.560 --> 00:04:41.440
like the amount of things you,
00:04:41.570 --> 00:04:42.520
oh, I have an idea.
00:04:43.740 --> 00:04:48.680
Like the chances like to just throw a bunch of agents into that and see what happens.
00:04:49.200 --> 00:04:49.840
It's very dangerous.
00:04:50.240 --> 00:04:50.820
It's very dangerous.
00:04:51.240 --> 00:04:52.040
I have an idea.
00:04:52.040 --> 00:04:52.840
It'll probably take me an hour.
00:04:52.880 --> 00:04:54.080
Let me see if I can validate it.
00:04:54.520 --> 00:04:57.080
Thursday afternoon, you're still like, oh, I'm still almost there.
00:04:57.120 --> 00:04:58.980
It's like, where did my week go?
00:04:59.280 --> 00:04:59.500
What happened?
00:05:00.360 --> 00:05:01.420
It's fun though, isn't it?
00:05:01.500 --> 00:05:02.360
It's a wild time.
00:05:02.720 --> 00:05:05.000
Yeah, it's weird, right?
00:05:05.200 --> 00:05:12.800
Because in a way, we have this very powerful, not super constant tool.
00:05:14.080 --> 00:05:19.760
but it's very powerful and and sometimes just amazing like sometimes like what do you what
00:05:19.760 --> 00:05:27.160
you get back is just amazing and you say like oh okay that's nice that's unexpected sometimes is
00:05:27.280 --> 00:05:34.960
the dumbest thing possible you can you you can get but i guess like the point the main point is that
00:05:35.240 --> 00:05:42.860
i think it it shows up how as humans we have like a complicated relationship with tools
00:05:43.860 --> 00:05:51.980
it can exploit a bunch of weird human behavior sometimes so yeah yeah if you like building things
00:05:52.520 --> 00:05:56.560
you know create you've got ideas and you want to see them come to life it's an unprecedented time
00:05:56.720 --> 00:06:03.220
if if you really your main joy was to be in the code working on the little bits of nuance i can
00:06:03.300 --> 00:06:07.300
see that it's frustrating and uh there's there's a whole there's a whole thing that we go into there
00:06:07.260 --> 00:06:08.120
but that's probably not.
00:06:08.130 --> 00:06:09.660
But wow, what a crazy time, right?
00:06:09.900 --> 00:06:10.900
Yeah, absolutely.
00:06:11.400 --> 00:06:14.000
Yeah, so let's jump into the topics here.
00:06:14.010 --> 00:06:18.560
I think the first thing I want to do is let's just do a little dive into Granian.
00:06:19.320 --> 00:06:21.520
That's why I reached out to you to have you on the show the first time.
00:06:21.840 --> 00:06:23.160
Like I said, it's powering.
00:06:23.390 --> 00:06:25.460
I have on the Talk Python server,
00:06:25.680 --> 00:06:29.680
there's 33 different containers running a bunch of different things,
00:06:29.770 --> 00:06:32.440
and probably about 15 of them are running Granian.
00:06:33.020 --> 00:06:33.380
Nice.
00:06:33.880 --> 00:06:34.160
Yeah.
00:06:34.800 --> 00:06:36.820
So like I said, it's been really, really cool.
00:06:37.040 --> 00:06:41.400
just tell people what is Granian because I think not only is it interesting as a thing that you've
00:06:41.400 --> 00:06:45.580
done previously and been on the show before it's also an interesting lead-in to where we're going
00:06:45.580 --> 00:06:52.940
with Tonio so what's great yeah sure uh so Granian is an application server for Python so in Python
00:06:53.540 --> 00:07:02.700
for historical reasons um we separated like the application layer from the HTTP layer so when we
00:07:02.600 --> 00:07:07.640
want to serve a web application in Python compared to other languages
00:07:07.740 --> 00:07:12.260
in which the server part is actually a part of the application.
00:07:12.640 --> 00:07:18.640
In Python, we tend to have a separated package, which is a server,
00:07:19.100 --> 00:07:22.340
and that server's import your actual application.
00:07:22.760 --> 00:07:25.820
And then we have application protocols
00:07:26.720 --> 00:07:30.120
to make the server talk with your application and back.
00:07:31.100 --> 00:07:34.960
The two main protocols in Python are VUSGI or WSGI.
00:07:35.500 --> 00:07:38.120
I never really understood how to say that,
00:07:39.720 --> 00:07:44.760
which is like the original PET333, if I'm not mistaken.
00:07:45.050 --> 00:07:48.100
So it's quite ancient in the Python history.
00:07:49.140 --> 00:07:53.860
And once we had like AsyncIO, a new protocol emerged.
00:07:54.380 --> 00:07:57.660
That's called ASGI or ASGI because, of course,
00:07:57.780 --> 00:08:00.080
it's the asynchronous server gateway.
00:08:00.520 --> 00:08:05.260
So, Granian is, again, an application server,
00:08:06.360 --> 00:08:11.720
so you can consider it as an alternative to MicroVoosgi,
00:08:13.860 --> 00:08:17.600
Uvicorn, Hypercorn, and a bunch of other servers
00:08:17.820 --> 00:08:19.240
that happened during the years.
00:08:20.520 --> 00:08:24.640
And, yeah, the peculiar fact is it's made with Rust.
00:08:26.080 --> 00:08:29.880
So, compared to all the other servers you might use in Python,
00:08:29.960 --> 00:08:34.659
all the HTTP part or the network layer
00:08:35.300 --> 00:08:39.780
is running completely out of the interpreter,
00:08:40.940 --> 00:08:46.020
which kind of alleviates the load on the interpreter,
00:08:46.230 --> 00:08:47.320
like on the Python side.
00:08:48.550 --> 00:08:50.680
And depending on the context,
00:08:50.860 --> 00:08:53.420
it might be more performant
00:08:53.660 --> 00:08:57.500
or it might stabilize your latency more.
00:08:58.660 --> 00:09:03.360
You know, I think thinking back now, I'm pretty sure that that was the reason I chose Granian.
00:09:03.770 --> 00:09:09.420
Not that it was a ton faster, but the P95 bad side of things.
00:09:09.550 --> 00:09:13.260
Like how much might it slow down under certain weird circumstances?
00:09:13.760 --> 00:09:17.800
It was way more stable than a lot of the other servers, right?
00:09:17.870 --> 00:09:19.220
And that's kind of what you're referring to.
00:09:19.820 --> 00:09:23.640
Yeah, because again, like a normal server which runs everything into Python,
00:09:23.720 --> 00:09:39.480
Like it has this kind of side effects that when your application is starting becoming slow, it kind of becomes like an infinite loop in which like the server adds additional load to the interpreter, which is already like highly loaded from your application.
00:09:39.860 --> 00:09:49.940
And so like Graniac kind of avoids all of that because like even if your application is fully loaded, like all the web and network layer is out of the interpreter.
00:09:50.100 --> 00:09:52.880
It doesn't disturb, let's say the interpreter with that.
00:09:53.200 --> 00:09:53.280
Yeah.
00:09:53.540 --> 00:09:58.080
I do want to just give a little bit of a hat tip to WSGI or WSGI.
00:09:58.450 --> 00:10:14.240
Because, yeah, you pointed out it is old, but it's really, really nice that if somebody is out there running on GUnicorn or they're running on some other server, MicroWSGI, which you should stop running MicroWSGI because it's not supported anymore.
00:10:14.600 --> 00:10:16.580
You don't have to change your code at all.
00:10:16.760 --> 00:10:20.480
You just take your flask or your Jenga or whatever, and you just say, now run here.
00:10:20.510 --> 00:10:24.740
And they just, Grandin talks WizGey or Asggy to that thing, and it's transparent.
00:10:25.330 --> 00:10:25.720
It's beautiful.
00:10:26.140 --> 00:10:26.240
Yeah.
00:10:26.510 --> 00:10:26.620
Yeah.
00:10:26.940 --> 00:10:33.659
I mean, there are like still some, you know, sometimes weird things like, like, Vertzoic
00:10:34.500 --> 00:10:37.680
has some subtleties, sometimes some nuances.
00:10:37.840 --> 00:10:50.700
So I had to like add some environment key just for Verzoic because under some circumstances, if that key is not there, things don't work exactly good.
00:10:51.120 --> 00:11:02.120
So yes, I agree that like having like the two protocols separated and coded like in a specific way gives you this ability to switch the server.
00:11:02.520 --> 00:11:08.180
On the other hand, sometimes, you know, you can be a pain in some time.
00:11:08.680 --> 00:11:09.400
Yeah, for sure.
00:11:09.840 --> 00:11:10.460
Yeah, for sure.
00:11:11.780 --> 00:11:18.380
But yeah, I'd say like we like recently it's getting more popular.
00:11:18.690 --> 00:11:27.540
So I'd say if I'm not mistaken, like a few weeks ago, I think I surpassed like HyperCorn in terms of downloads.
00:11:28.760 --> 00:11:28.980
Awesome.
00:11:29.060 --> 00:11:37.480
And yeah, it's used like in a bunch of different big companies, Microsoft, Google, for sure.
00:11:38.350 --> 00:11:40.000
We use it at Sentry, of course.
00:11:41.140 --> 00:11:46.040
And I think like Sentry is the only company in which we use like all the three supported
00:11:46.380 --> 00:11:51.300
protocols of Granian because Granian also supports like its own protocol, which is named
00:11:51.410 --> 00:11:56.320
like RSGI, I don't know, like RSGI, which is a redesign.
00:11:56.740 --> 00:11:56.940
Risky.
00:11:57.840 --> 00:11:57.940
Yeah.
00:12:00.220 --> 00:12:07.460
um so yeah i'd say it's going pretty good um i think like the last let's say this year was mostly
00:12:07.800 --> 00:12:16.340
into um not not not not mostly about features i think features but mostly about you know making
00:12:16.480 --> 00:12:22.400
the thing more stable and covering like some edge cases um especially again we're using it the
00:12:22.460 --> 00:12:28.260
Sentry. So now like if something is wrong with Grinia, like I have people knocking on my door.
00:12:28.900 --> 00:12:34.900
I bet you do. Well, I mean, with Sentry, Sentry gets an insane amount of like an unimaginable
00:12:35.040 --> 00:12:39.480
amount of traffic. I'm sure with the error reporting side of things and probably a little
00:12:39.580 --> 00:12:46.040
more so these days on the MCP and API side as well with all the agents. I know that I use the MCP
00:12:46.120 --> 00:12:50.580
and absolutely love it. It's been incredible. It's really, really good. I could just say on my
00:12:50.600 --> 00:12:54.720
project hey there's a century error i got what's up like literally that's all i gotta say in clause
00:12:54.840 --> 00:12:57.920
like hold on we're on it and it's like yeah you know five minutes later it's like here's what's
00:12:57.930 --> 00:13:02.580
going on let's work on it you know yeah really good stuff uh i do want to give a quick shout out as
00:13:02.700 --> 00:13:08.220
well uh on this regard to gradient like people are like oh it's it's not as popular as server x or
00:13:08.310 --> 00:13:12.120
whatever right although i think that's starting to fade it's it's got 5 000 stars and a ton of
00:13:12.210 --> 00:13:19.079
people using it and so on yeah but it's also based on hyper from rust which itself has uh where where's
00:13:19.100 --> 00:13:25.420
my numbers, 410,000 projects using it. So it's really tested, right? And obviously the stuff at
00:13:25.500 --> 00:13:31.640
Sentry as well. Yeah. Yeah. Like the only thing I'm sure about Granian is that like the HTTP stack,
00:13:32.100 --> 00:13:38.280
that's super safe. Like that's like, I could bet like everything I have on that. So if something
00:13:38.300 --> 00:13:44.660
is wrong in Granian, like that's on me, not on Hyper, like to be clear. This portion of Talk
00:13:44.660 --> 00:13:49.660
Python to me is brought to you by Sentry and Sear AI. There are plenty of AI tools that help you
00:13:50.080 --> 00:13:55.280
write code, but Sentry Sear is built to help you fix it when it breaks. The difference is context.
00:13:55.920 --> 00:14:00.500
Sear isn't just guessing based on syntax. It's analyzing your actual Sentry data,
00:14:00.760 --> 00:14:05.360
your stack traces, logs, and failure patterns. Because it has the full context, it can
00:14:05.740 --> 00:14:09.620
A, spot buggy code in review and help prevent issues before they happen,
00:14:10.260 --> 00:14:13.040
and B, identify the root cause of production errors.
00:14:13.980 --> 00:14:18.540
It can even draft a fix and hand the work off to an agent-like cursor to open a PR for you.
00:14:19.140 --> 00:14:21.140
Seer turns Sentry into a complete loop.
00:14:21.660 --> 00:14:24.500
You have your traces, errors, logs, and replays to see the problem,
00:14:24.800 --> 00:14:26.260
and now AI to help solve it.
00:14:26.620 --> 00:14:30.920
Join millions of devs at companies like Claude, Disney+, and even Talk Python,
00:14:31.380 --> 00:14:32.560
who use Sentry to move faster.
00:14:33.080 --> 00:14:37.860
Check them out at talkpython.fm/sentry and use code talkpython26,
00:14:38.440 --> 00:14:41.800
all one word, for $100 in Sentry credits.
00:14:42.440 --> 00:14:44.180
Thank you to Sentry for supporting Talk Python.
00:14:45.660 --> 00:14:48.980
I recently did a post over on my personal blog
00:14:49.080 --> 00:14:52.860
calling cutting Python web app memory by 31%.
00:14:52.860 --> 00:14:55.760
And I did a ton of analysis and stuff
00:14:56.660 --> 00:14:59.260
on how I was running Granian and different things,
00:14:59.300 --> 00:15:00.240
just how I was running Python.
00:15:00.820 --> 00:15:02.400
And I think this is going to be a good lead-in
00:15:02.400 --> 00:15:03.540
to where we're going as well.
00:15:03.880 --> 00:15:13.040
I got it to where I was using 1.3 gigs, let's say, for Talk Python training the courses specifically.
00:15:13.360 --> 00:15:15.160
And people are like, oh, it's just a little courses thing.
00:15:15.200 --> 00:15:19.960
Like, well, it has 178,000 lines of Python, so it's not a totally small project.
00:15:20.440 --> 00:15:23.240
But down by the end, I did a bunch of stuff on it.
00:15:24.480 --> 00:15:25.280
Lazy imports.
00:15:25.779 --> 00:15:28.660
I switched to async, which is why I brought this up.
00:15:28.940 --> 00:15:31.160
But somewhere down here, I don't know what the final number is.
00:15:31.400 --> 00:15:31.820
Here we go.
00:15:31.880 --> 00:15:36.560
In the end, I got it down to 450 megs from 0.28 gigs.
00:15:37.190 --> 00:15:40.900
One of the main reasons I did that as a way I want to kind of want to lead into like this,
00:15:41.380 --> 00:15:45.740
where we're going is I said, well, the way grading works is for each,
00:15:46.250 --> 00:15:49.540
either you can make multi-processes that are single threaded or it could do multiple threads
00:15:49.660 --> 00:15:51.380
and then it'll create a separate interpreter for each one.
00:15:51.490 --> 00:15:55.240
Either way, it kind of like replicates out the running.
00:15:55.420 --> 00:15:58.660
It'll create copies in one way or another of your code.
00:15:59.010 --> 00:16:01.760
And that, if you could run one of them, that would be great.
00:16:01.840 --> 00:16:03.000
But why do we have so many?
00:16:03.330 --> 00:16:04.900
We have so many because of the GIL.
00:16:05.010 --> 00:16:06.140
We have poor concurrency.
00:16:06.640 --> 00:16:10.360
It's really hard to handle a lot of load and like actually access, you know, my servers,
00:16:10.600 --> 00:16:10.920
eight cores.
00:16:11.010 --> 00:16:14.060
Like I could only access one core like normally, right?
00:16:14.380 --> 00:16:17.560
And so we do all these things to like explode them out.
00:16:17.680 --> 00:16:18.880
But what is the most expensive?
00:16:19.140 --> 00:16:22.160
Like five years ago, this was true on servers, but really is true now.
00:16:22.250 --> 00:16:23.000
What is most expensive?
00:16:23.360 --> 00:16:24.380
Memory, not compute, right?
00:16:25.180 --> 00:16:25.300
Yeah.
00:16:25.700 --> 00:16:25.760
Yeah.
00:16:26.160 --> 00:16:26.600
Thanks AI.
00:16:27.000 --> 00:16:27.540
Thanks AI.
00:16:27.710 --> 00:16:27.940
Thanks Altman.
00:16:28.860 --> 00:16:30.120
We really appreciate that.
00:16:32.260 --> 00:16:34.580
But we've free-threaded Python now.
00:16:34.780 --> 00:16:35.300
That's a thing.
00:16:35.740 --> 00:16:43.280
And so we're kind of moving into this world where we can take better advantage of the hardware and we don't have to multiply out.
00:16:44.220 --> 00:16:47.060
I know your process takes 500 megs of RAM, but we need four of them.
00:16:47.090 --> 00:16:49.580
So now you need two gigs just to run your code.
00:16:50.360 --> 00:16:51.940
We don't necessarily need that anymore.
00:16:52.160 --> 00:16:53.780
And grading would be a really good foundation for that.
00:16:54.000 --> 00:16:59.440
But let's just think broadly about free-threading and async as it is and stuff.
00:16:59.660 --> 00:17:02.620
I know you've thought a lot about this because of this project that you built.
00:17:03.680 --> 00:17:04.819
Well, what do you think?
00:17:05.350 --> 00:17:08.720
First thing, can I say free threading to me?
00:17:09.140 --> 00:17:11.199
It's super exciting.
00:17:11.640 --> 00:17:16.060
And I don't know why people are not as much excited as me on this.
00:17:16.819 --> 00:17:25.480
Because this is, I'd say, the biggest thing that ever happened into Python since the beginning of Python.
00:17:25.680 --> 00:17:27.140
In the last 30 years.
00:17:27.380 --> 00:17:31.720
do you remember like Python 2 to Python 3 and all like,
00:17:32.080 --> 00:17:36.360
oh, now strings are not bytes anymore and everything is Unicode?
00:17:36.700 --> 00:17:36.800
Yeah.
00:17:37.040 --> 00:17:40.960
That's nothing compared to, hey, the gil is not there anymore.
00:17:41.080 --> 00:17:46.800
Like we talk about like every Python room everywhere in the world,
00:17:46.860 --> 00:17:53.220
like at some point talked about the gil and why we have to do to have the gil, right?
00:17:53.960 --> 00:17:54.160
Right.
00:17:54.720 --> 00:17:59.220
The Gilectomy that Larry Hastings had, it seemed to perpetually be working on and never
00:17:59.440 --> 00:18:00.020
kind of went anywhere.
00:18:00.340 --> 00:18:00.480
Yeah.
00:18:00.480 --> 00:18:05.660
It took like 15 years of people talking about this and it finally landed.
00:18:05.860 --> 00:18:12.000
Like last year, yes, of course, last year it was beta because like in Python 3.13, it
00:18:12.000 --> 00:18:14.380
was like, this is an experiment still.
00:18:14.900 --> 00:18:17.840
That was the asterisk on top.
00:18:18.960 --> 00:18:22.120
But since Python 3.14, that's considered stable.
00:18:22.540 --> 00:18:28.560
It's exactly as the normal GIL-included interpreter.
00:18:30.480 --> 00:18:33.540
And at the end of the day, the biggest part of all of this
00:18:33.800 --> 00:18:38.800
was using reference counting instead of singleton.
00:18:39.160 --> 00:18:44.000
So I guess I'm excited about free-threaded Python
00:18:44.020 --> 00:18:47.840
because finally I have the language I love the most.
00:18:49.020 --> 00:18:53.180
not because of the structure, not because of types or how it's designed,
00:18:53.180 --> 00:18:57.840
but because of this awesome community that only Python language has,
00:18:58.220 --> 00:18:59.080
at least in my experience.
00:19:00.220 --> 00:19:04.080
And so finally, I can use this language with actual threads.
00:19:04.540 --> 00:19:08.460
So I'm not obliged anymore to think, oh, wait, we are in Python,
00:19:08.920 --> 00:19:13.760
so I need to use a C library or something to take advantage of my CPU,
00:19:14.480 --> 00:19:25.040
Especially given, I don't know, we are in the era of, hey, even if you buy a $200 CPU that probably has 12 cores or something.
00:19:26.380 --> 00:19:28.420
So yeah, I'm very excited.
00:19:29.700 --> 00:19:32.740
I'm confused about the lack of adoption.
00:19:33.160 --> 00:19:35.260
I would love to see more adoption in this.
00:19:35.500 --> 00:19:46.120
And of course, like there are reasons, like every native C compiled extension has to be fixed to be thread safe.
00:19:47.640 --> 00:19:53.360
And probably like on a bunch of ancient extensions, that's not simple or easy.
00:19:55.180 --> 00:19:56.240
Yeah, I don't know, man.
00:19:56.320 --> 00:19:58.100
Like I'm just too excited.
00:19:58.380 --> 00:20:00.620
Like I want people to use free thread.
00:20:00.800 --> 00:20:02.020
I know.
00:20:02.180 --> 00:20:04.260
And getting it is so much easier as well.
00:20:04.340 --> 00:20:10.840
it's just uv Python install, you know, that 3.14 T and you got it, right?
00:20:10.980 --> 00:20:11.200
Yeah.
00:20:11.600 --> 00:20:11.720
Yeah.
00:20:12.080 --> 00:20:16.740
And like, I don't know, I'm so pissed at the maintainer of the Docker images
00:20:16.880 --> 00:20:20.240
because we don't have like an official 3.14 free threaded image.
00:20:20.360 --> 00:20:20.660
Like why?
00:20:21.160 --> 00:20:21.220
Why?
00:20:21.400 --> 00:20:23.460
Oh yeah, that is actually a good point.
00:20:23.580 --> 00:20:28.900
Like it has been a year and a half and it just requires putting the T on the uv installed bit.
00:20:29.160 --> 00:20:29.440
Yeah.
00:20:29.740 --> 00:20:29.860
Yeah.
00:20:30.320 --> 00:20:30.460
Yeah.
00:20:30.480 --> 00:20:36.660
Yeah, but again, to my perspective, free-threaded Python opens the window to do a bunch of...
00:20:36.840 --> 00:20:42.040
First, to solve a bunch of the issues you mentioned, which is, for example, in Centri,
00:20:42.160 --> 00:20:43.980
the number one issue is...
00:20:44.120 --> 00:20:50.460
I mean, Centri at the end of the day is this 18-years-old Django monolith.
00:20:51.420 --> 00:20:59.520
And so, yes, we deploy in several multiple different deployments, activating just some parts.
00:20:59.720 --> 00:21:20.080
But even so, like, yeah, we have the same problem, which is, hey, like, in order to use like one C view out of like a VM that GCP gives us, like, yeah, we need to spawn like four different processes and require like, I don't know, four, five, six gigabytes of memory for like a single pod.
00:21:20.320 --> 00:21:31.320
And not only do you have to do that, you also have to, anytime there's some sort of in-memory cache, like an LRU cache or something, every one of those has to be spun up for all four, not just for one, right? There's a lot of powerful sharing.
00:21:31.440 --> 00:21:34.120
Do we want to start talking about observability?
00:21:34.420 --> 00:21:36.580
Because you cannot just use like
00:21:36.640 --> 00:21:39.380
the Prometheus SDK or anything like that.
00:21:40.000 --> 00:21:42.000
Because again, like then those became like
00:21:42.480 --> 00:21:43.340
four different instances.
00:21:43.600 --> 00:21:45.800
So you cannot even track like the same metrics
00:21:46.620 --> 00:21:48.480
unless you do like some weird tricks.
00:21:48.960 --> 00:21:53.200
Like, yeah, relying on the multiprocessing library.
00:21:54.180 --> 00:21:54.800
It's messy, man.
00:21:54.980 --> 00:21:57.080
So like, yeah, I think like
00:21:57.620 --> 00:22:00.220
FreeThreader like solves a bunch of problems,
00:22:00.820 --> 00:22:04.180
Especially in web development in Python.
00:22:05.420 --> 00:22:09.780
Because everybody says like, oh yeah, they did this just because of AI, you know,
00:22:09.880 --> 00:22:12.240
because they can like make things parallel, whatever.
00:22:12.720 --> 00:22:14.200
I don't get that.
00:22:14.300 --> 00:22:18.280
Like, like we have like this amazing feature and it solves a bunch of problem
00:22:18.460 --> 00:22:23.180
on web development and also like it opens up the a bunch of new opportunities.
00:22:23.400 --> 00:22:27.680
Because again, like one of the most annoying parts in Granian, which is also
00:22:27.700 --> 00:22:33.400
Like one of the things that people opens like the most, the vast majority of issues about
00:22:33.580 --> 00:22:38.960
is because nobody understands like how socket sharing works across different processes.
00:22:39.370 --> 00:22:45.100
And again, like with FreeThreader, like that's just go away if we have something different
00:22:45.230 --> 00:22:45.940
from a Synco.
00:22:46.310 --> 00:22:51.280
And I guess that's the, that's when we get on the point here.
00:22:52.520 --> 00:22:57.660
Because for example, like in Grainium, so Grainium supports FreeThreader Python scenes
00:22:57.680 --> 00:22:59.440
2.0, if I'm not mistaken.
00:22:59.920 --> 00:23:01.520
And so when you use
00:23:01.700 --> 00:23:03.420
Granian on a free-threaded interpreter,
00:23:03.840 --> 00:23:05.520
workers become
00:23:07.600 --> 00:23:07.660
threads.
00:23:08.200 --> 00:23:09.340
They're no longer processes.
00:23:10.200 --> 00:23:11.520
And I can skip all the dance
00:23:11.620 --> 00:23:13.160
of, yeah, open the socket,
00:23:14.140 --> 00:23:15.500
but just bind
00:23:15.500 --> 00:23:17.440
on that, but not on listen, because then
00:23:17.640 --> 00:23:19.440
it gets to be shared across processes
00:23:19.960 --> 00:23:21.500
and so each worker can have his own
00:23:21.660 --> 00:23:23.540
backlog. It's a mess, man.
00:23:23.740 --> 00:23:25.700
So on the free-threaded variant,
00:23:26.200 --> 00:23:28.100
Like, Grenian already, like, used threads.
00:23:29.799 --> 00:23:35.280
The subtlety is that each thread has its own event loop.
00:23:35.510 --> 00:23:41.280
So what Grenian cannot do today is sharing things between those threads.
00:23:41.560 --> 00:23:49.180
At the end of the day, yes, the application is shared, but, like, every request, like, if a repus comes to a worker, that's it.
00:23:49.250 --> 00:23:50.120
Like, there's no...
00:23:50.310 --> 00:23:51.780
You cannot cross boundaries, right?
00:23:52.060 --> 00:23:54.040
when you await for something,
00:23:54.130 --> 00:23:55.260
when you suspend for something,
00:23:56.440 --> 00:23:57.500
that's the loop,
00:23:57.750 --> 00:23:59.020
the event loop you're running into.
00:23:59.980 --> 00:24:01.540
Which is a big limitation for servers.
00:24:02.300 --> 00:24:03.320
Yeah, and one is calling await
00:24:03.540 --> 00:24:04.400
and another is calling await
00:24:04.680 --> 00:24:06.060
or maybe doing some work,
00:24:06.260 --> 00:24:07.860
but if they were the same event loop,
00:24:08.220 --> 00:24:09.180
one thing could run
00:24:09.420 --> 00:24:10.240
because the other is awaiting,
00:24:10.380 --> 00:24:12.400
but they're actually separate altogether, right?
00:24:12.560 --> 00:24:15.460
Yeah, and also you can still end up
00:24:15.460 --> 00:24:16.720
in these weird conditions
00:24:16.930 --> 00:24:19.360
in which maybe, I don't know,
00:24:19.440 --> 00:24:21.139
50% of your connection
00:24:21.740 --> 00:24:24.640
we're bound to a specific event loop.
00:24:25.180 --> 00:24:28.080
And so you also have like unbalancement
00:24:28.190 --> 00:24:29.420
between like the different threads.
00:24:29.510 --> 00:24:30.640
So you have like one thread
00:24:30.800 --> 00:24:32.880
that receives the vast majority of things,
00:24:33.060 --> 00:24:34.100
the other thread doing nothing.
00:24:35.480 --> 00:24:35.640
Yeah.
00:24:35.860 --> 00:24:36.000
Yeah.
00:24:36.390 --> 00:24:38.380
Let's say like we have new opportunities.
00:24:39.200 --> 00:24:41.080
We kind of lack some libraries,
00:24:41.370 --> 00:24:42.700
like some libraries behavior
00:24:43.700 --> 00:24:45.620
that can actually give us like
00:24:45.880 --> 00:24:47.580
all the actual meat here.
00:24:48.600 --> 00:24:48.900
Agreed.
00:24:49.020 --> 00:24:53.600
I want to talk about one area before we dive in, the Tone.io,
00:24:53.820 --> 00:24:55.740
and that's just asyncio event loops.
00:24:56.120 --> 00:24:57.460
And I know during your presentation,
00:24:57.700 --> 00:25:00.240
so you gave a talk on this at EuroPython this year,
00:25:00.340 --> 00:25:04.120
and I'll link to the topic right here in the moment.
00:25:05.140 --> 00:25:06.920
But there are people like, oh, well, we have uvloops,
00:25:07.040 --> 00:25:08.300
so doesn't that kind of solve it?
00:25:08.500 --> 00:25:10.980
And I think that's a really interesting thought
00:25:11.180 --> 00:25:14.080
because without calling out anybody, I didn't intend to call out anybody.
00:25:14.240 --> 00:25:18.540
I think it just highlights in the Python space,
00:25:18.940 --> 00:25:22.240
There's just not a lot of thinking about what concurrency means,
00:25:22.550 --> 00:25:24.660
why it matters, how you program for it.
00:25:25.460 --> 00:25:26.760
And that's going to be a challenge.
00:25:27.020 --> 00:25:28.620
I think that's part of the challenge of what you're talking about
00:25:28.660 --> 00:25:30.180
with the adoption of free-threaded Python.
00:25:30.330 --> 00:25:34.380
But I think also it is a challenge in the sense that
00:25:34.720 --> 00:25:37.520
thinking about making the event loop faster by, I don't know,
00:25:37.720 --> 00:25:39.900
20% or whatever uvloop actually does, which is great,
00:25:40.100 --> 00:25:43.300
it doesn't really address the fact that you're still only on one thread.
00:25:43.800 --> 00:25:47.400
Your event loop can access, like, I just, against probably better judgment,
00:25:47.600 --> 00:25:51.120
I just bought a Mac Studio Max, which has 18 cores.
00:25:51.559 --> 00:25:55.700
If I write one thread, that's about like 5% of the capacity of that machine.
00:25:56.300 --> 00:26:01.600
So what does it matter if it's 4.9% or 5.1% depending on which loop?
00:26:01.840 --> 00:26:02.520
I don't care.
00:26:02.640 --> 00:26:04.120
That is broken either way.
00:26:04.280 --> 00:26:06.400
I want 95% capacity.
00:26:06.780 --> 00:26:07.080
Yes.
00:26:07.280 --> 00:26:07.560
Right?
00:26:07.940 --> 00:26:08.920
And that's, I don't know.
00:26:08.960 --> 00:26:11.940
I thought it would be worthwhile to kind of like talk about uvloop a little bit.
00:26:12.020 --> 00:26:12.940
And you built our loop.
00:26:13.100 --> 00:26:14.860
And those things are great in their building blocks.
00:26:14.960 --> 00:26:15.100
Yeah.
00:26:15.180 --> 00:26:16.820
They're not the panacea.
00:26:16.920 --> 00:26:17.440
They're not the fix.
00:26:17.860 --> 00:26:25.900
Yeah, and we also now have Marcelo Trzesitsky, the maintainer of UVCore and Starlet.
00:26:27.680 --> 00:26:39.500
It has recently published Zoovloop, which is another event loop for Python relying on libUV, but it's written in Zig.
00:26:40.900 --> 00:26:44.280
But yeah, as you said, all of these event loops,
00:26:46.220 --> 00:26:52.580
yes, they can optimize some of the hot part of the cycle.
00:26:53.760 --> 00:26:55.320
But basically, that's it.
00:26:55.820 --> 00:27:00.960
Nothing really changed the shape of how things work.
00:27:02.080 --> 00:27:07.939
And so, yes, for sure, you should actually install
00:27:08.100 --> 00:27:13.780
uvloop or r loop or zoo v loop or whatever to to speed up like because that's performance you're
00:27:13.820 --> 00:27:19.320
leaving on the table regardless right like so uh it's good to have this project because they also
00:27:19.480 --> 00:27:26.500
like can light uh some of the inefficiencies of a sink io in my opinion um so i think it's still
00:27:26.550 --> 00:27:31.960
good to have these projects and you should use them um but yeah they want like the best you can
00:27:31.920 --> 00:27:39.520
GAT is probably like in the order of like 20% in, and we're talking about like row throughput
00:27:39.940 --> 00:27:46.180
if you're just doing TCP and nothing else, which is nobody does like, like nothing does
00:27:46.380 --> 00:27:47.020
nothing out there.
00:27:47.180 --> 00:27:47.240
Right?
00:27:47.320 --> 00:27:48.800
Like we do a bunch of stuff.
00:27:49.160 --> 00:27:49.400
Yeah.
00:27:49.480 --> 00:27:52.980
If you construct an example where basically all you're doing is waiting very efficiently,
00:27:53.410 --> 00:27:55.180
then you start to see those, those deviate.
00:27:55.180 --> 00:27:59.460
And also if you've got, like, I've got 10 tasks that run over two seconds.
00:27:59.940 --> 00:28:00.660
It does zero.
00:28:00.690 --> 00:28:01.220
It doesn't matter.
00:28:01.600 --> 00:28:05.080
This only matters when you're doing very, very fine-grained work and tons of it,
00:28:05.080 --> 00:28:07.900
and there's lots of switching and that sort of thing, right?
00:28:08.160 --> 00:28:09.080
Yes, yes.
00:28:11.120 --> 00:28:16.140
And so, yes, I guess this was one of my realizations.
00:28:16.570 --> 00:28:20.300
Because, again, I worked on ArtLoop mainly last year.
00:28:21.470 --> 00:28:24.900
And working on ArtLoop made me realize two things.
00:28:25.860 --> 00:28:31.260
The first thing is that asimkayo, it's overcomplicated sometimes.
00:28:31.540 --> 00:28:39.160
It's so fun fact at the presentation I did about that loop in Python Italy 25.
00:28:40.450 --> 00:28:51.900
I had a slide asking to the audience, what's the difference between a protocol and a transport in Async.io.
00:28:52.220 --> 00:29:01.300
Because if you check the official documentation of Python and you get to that chapter, you will find out that they have like to try to explain you that difference.
00:29:01.860 --> 00:29:05.280
they try to do that like in three different ways,
00:29:06.340 --> 00:29:07.720
which to my experience,
00:29:07.880 --> 00:29:10.320
like if you need three different ways
00:29:10.380 --> 00:29:11.620
to explain the same thing,
00:29:12.020 --> 00:29:13.800
something is deeply wrong.
00:29:14.620 --> 00:29:17.860
Definitely violates an Einstein core philosophy.
00:29:18.180 --> 00:29:18.360
Yes.
00:29:19.260 --> 00:29:20.400
Also because like, again,
00:29:20.660 --> 00:29:22.460
if we think about the Zen of Python,
00:29:23.140 --> 00:29:25.280
like simple is better than complex, right?
00:29:27.000 --> 00:29:29.440
So that was my first realization, right?
00:29:30.440 --> 00:29:35.480
Because, again, like you have a bunch of primitives, like you have events, futures.
00:29:37.400 --> 00:29:43.880
Oh, by the way, every time they get spawned into the event loop, they get wrapped into a task.
00:29:44.200 --> 00:29:45.780
What is a task? Nobody knows.
00:29:46.920 --> 00:29:51.220
Oh, and by the way, when they actually run inside the event loop, they became handles.
00:29:52.380 --> 00:29:59.820
So we have a bunch of this weird thing that they're not really exposed to you if you just write async code.
00:30:00.180 --> 00:30:06.020
But if you need to debug something when something is wrong, then you need all of that to understand what's going on, right?
00:30:06.800 --> 00:30:14.120
And the other realization, again, was, okay, I reached a point in which there's no other possible optimization in our book.
00:30:14.300 --> 00:30:20.620
Because the vast majority of CPU time spent here is around AsyncIO primitives.
00:30:21.000 --> 00:30:22.560
And I have one single thread.
00:30:22.960 --> 00:30:28.920
And so, like, the only, when I came to realize this, I just said, okay, wait a second.
00:30:29.100 --> 00:30:35.020
Is that, is really that hard to start like scratch whiteboard?
00:30:35.420 --> 00:30:40.380
Like from the beginning, like let's just assume for a second, like asyncio never existed.
00:30:40.720 --> 00:30:42.920
And I want to do like async stuff in Python.
00:30:43.160 --> 00:30:43.860
What that look like?
00:30:43.980 --> 00:30:47.300
Like what, what does it mean to make like an asynchronous runtime in Python?
00:30:47.540 --> 00:30:48.100
Is that hard?
00:30:48.420 --> 00:30:55.119
And I don't know, like, I guess three or four weeks after this, I just got like an asynchronous
00:30:55.140 --> 00:30:57.040
runtime, a complete alternative
00:30:57.240 --> 00:30:59.040
asynchronous runtime running on Python with
00:30:59.220 --> 00:30:59.700
multiple threads.
00:31:01.280 --> 00:31:03.080
To be clear on free-threaded Python, right?
00:31:03.320 --> 00:31:05.160
Yes, free-threaded Python only because
00:31:05.650 --> 00:31:07.160
again, to have like real threads
00:31:07.530 --> 00:31:09.260
I mean, the design is to have
00:31:09.400 --> 00:31:11.120
real threads, so the moment you have
00:31:11.120 --> 00:31:12.860
the gill, I say, no, no, no,
00:31:13.200 --> 00:31:15.060
runtime error, change your
00:31:15.280 --> 00:31:17.200
interpreter because this is not good,
00:31:17.380 --> 00:31:17.520
right?
00:31:19.200 --> 00:31:21.080
This portion of Talk Python I May is brought to you by
00:31:21.300 --> 00:31:23.040
our AI tools. You know that
00:31:23.180 --> 00:31:25.100
thing where you ask an AI something about
00:31:25.120 --> 00:31:30.860
Python and it confidently tells you about a library version from 18 months ago? Well, we fixed that,
00:31:31.100 --> 00:31:37.480
at least for our shows. Talk Python and Python Bytes both have MCP servers now. Connect Talk Python
00:31:37.530 --> 00:31:43.780
and your AI can search over 550 episodes, full transcripts, every guest in the entire course
00:31:44.120 --> 00:31:49.820
catalog. Connect Python Bytes and you get almost 500 episodes of Python news going back to 2026,
00:31:50.520 --> 00:31:54.940
including every link we've ever put in the show notes. That means you can say things like,
00:31:55.060 --> 00:32:02.460
ask Talk Python what astral joining OpenAI means for uv or what has Python bytes said about uv
00:32:02.860 --> 00:32:06.740
and get real answers with real links, not hallucination. Name the show in your prompt
00:32:06.920 --> 00:32:12.900
and your AI knows exactly where to look. And if you live in the terminal, Talk Python also has a CLI
00:32:12.930 --> 00:32:20.900
too. One line, uv tool install talk-python-cli and then search episodes, transcripts, guests,
00:32:21.220 --> 00:32:26.880
courses without ever opening a browser. It's open source and it outputs text, JSON, and Markdown,
00:32:26.960 --> 00:32:31.980
so it feeds the AI tools that don't speak MCP yet. And here's the real reason I built it.
00:32:32.460 --> 00:32:38.320
Both shows cover around 10 years of Python history. The people, the decisions, the packages that took
00:32:38.380 --> 00:32:45.460
over, and the ones that quietly didn't. This enhanced access is free. No account, no API key,
00:32:45.560 --> 00:32:52.140
nothing to buy. This history should belong to all of us. Visit talkpython.fm and Python Bytes and
00:32:52.260 --> 00:32:56.840
click the MCP link in the nav bar. Connect them right now to your agents so that they'll be
00:32:57.080 --> 00:33:02.440
accessible anytime you need them in the future. And so yeah, that was like kind of the landscape
00:33:02.980 --> 00:33:09.180
in which I started building Tonio or Tonio. You can pronounce it like however you want. Like I'm
00:33:09.300 --> 00:33:13.540
Italian, so I say Tonio, of course. I think obviously, well, you have to do Tonio.
00:33:14.780 --> 00:33:15.980
So it's really good.
00:33:16.140 --> 00:33:21.900
But as it pairs to asyncio, tone IO also kind of like as a hat to that, right?
00:33:22.180 --> 00:33:22.300
Yeah.
00:33:22.560 --> 00:33:22.660
Yeah.
00:33:23.280 --> 00:33:24.040
But we have Trio.
00:33:24.460 --> 00:33:26.760
So that's not Trio.
00:33:28.020 --> 00:33:30.280
Trio is living there a little bit as well.
00:33:30.660 --> 00:33:30.740
Yeah.
00:33:30.800 --> 00:33:33.700
So this is a really interesting project that you have.
00:33:33.880 --> 00:33:37.140
And it basically says, what if we actually had threads?
00:33:37.560 --> 00:33:44.000
One of the things that's endlessly frustrated me about Python and asyncio and consequently
00:33:44.020 --> 00:33:48.220
I think the language implementation of async await is it's really good.
00:33:48.320 --> 00:33:49.200
I think, you know,
00:33:49.840 --> 00:33:52.940
the way it sort of turns async code into what looks like structurally
00:33:54.299 --> 00:33:57.060
serializable code or serial code is really, really nice.
00:33:57.320 --> 00:33:58.160
But the,
00:33:58.620 --> 00:34:02.800
the fact that the developer has to juggle loops and which loop and that loop.
00:34:02.920 --> 00:34:03.940
No, that's not the right loop.
00:34:04.240 --> 00:34:07.200
Like so many times I've been, Oh, I want to do this request.
00:34:07.640 --> 00:34:10.659
Oh, are you using court or an async web framework? You,
00:34:10.879 --> 00:34:12.840
there is an async loop, but not that one.
00:34:12.899 --> 00:34:14.720
you need the one created by the web framework.
00:34:14.860 --> 00:34:17.460
Like, oh, did you initialize the async database connection
00:34:18.240 --> 00:34:19.700
before it ran the web thing?
00:34:19.720 --> 00:34:21.100
And like, nope, that's the wrong one.
00:34:21.500 --> 00:34:24.320
And there's all this weird juggling that's just so janky.
00:34:25.000 --> 00:34:26.659
And I'm pretty sure it's janky
00:34:27.240 --> 00:34:28.940
because we didn't have true threads.
00:34:29.460 --> 00:34:31.500
And the possibility, oh, what if you crisscross this?
00:34:32.160 --> 00:34:33.340
Everything sort of falls apart.
00:34:33.540 --> 00:34:35.540
And if you could just say, look, it's just multi-threaded.
00:34:35.760 --> 00:34:37.740
And there is a thing that called the loop
00:34:37.780 --> 00:34:38.840
and that's where stuff runs.
00:34:39.300 --> 00:34:41.080
And I don't care who started it.
00:34:41.220 --> 00:34:41.760
It's our loop.
00:34:41.899 --> 00:34:47.020
When I need a loop, the runtime Python itself has a loop for me and I'll use it.
00:34:47.320 --> 00:34:47.419
Right.
00:34:47.720 --> 00:34:49.639
And I feel like you sort of took that philosophy a little bit.
00:34:49.679 --> 00:34:49.800
Right.
00:34:50.320 --> 00:34:50.480
Yeah.
00:34:50.639 --> 00:34:58.180
I'd say like the number one inspiration for Sonio is Tokyo, which is the number one Rust
00:34:58.460 --> 00:34:59.100
asynchronous runtime.
00:35:00.620 --> 00:35:03.600
That's probably because like I spend like in Rust a bunch of time.
00:35:04.900 --> 00:35:09.900
So yeah, like, I don't know, for example, like the weird thing to me is like, we still like,
00:35:09.980 --> 00:35:14.020
if you just want to do like an asynchronous program in Python,
00:35:14.410 --> 00:35:18.160
like a program, like let's say a script or, I don't know,
00:35:18.320 --> 00:35:21.680
like a simple CLI, we still need to do like a synch.io run.
00:35:22.320 --> 00:35:22.920
Like why?
00:35:23.800 --> 00:35:28.080
Like the, yeah, the amount of things you need to know
00:35:28.680 --> 00:35:33.220
and decide and stuff there is like completely different
00:35:33.440 --> 00:35:34.340
from other languages.
00:35:34.520 --> 00:35:34.600
Yeah.
00:35:34.690 --> 00:35:36.860
Because again, like think about JavaScript.
00:35:37.670 --> 00:35:38.520
You don't decide anything.
00:35:38.580 --> 00:35:40.180
Like that's async in JavaScript.
00:35:40.900 --> 00:35:41.260
That's true.
00:35:41.640 --> 00:35:43.140
Well, I'll tell you a little bit why you can't just,
00:35:43.460 --> 00:35:44.560
why you still got to call async run
00:35:44.720 --> 00:35:46.460
because there's like a foundational layer
00:35:46.980 --> 00:35:47.740
that's not present.
00:35:48.300 --> 00:35:50.120
And it's your job to write the foundation
00:35:50.420 --> 00:35:53.580
on which asyncio executes, right?
00:35:53.960 --> 00:35:55.080
Like it's still your job to figure,
00:35:55.220 --> 00:35:56.820
okay, how do I actually create a loop?
00:35:56.880 --> 00:35:57.640
How do I run a loop?
00:35:57.740 --> 00:35:59.120
How do I, like, it's your job.
00:35:59.520 --> 00:36:01.340
You can't just create an async def method
00:36:01.500 --> 00:36:04.100
and call it because there's nowhere for it to go
00:36:04.280 --> 00:36:05.440
until you go create, you know what I mean?
00:36:05.540 --> 00:36:06.120
There's like, there's just,
00:36:06.800 --> 00:36:08.660
But the foundation isn't quite there.
00:36:08.960 --> 00:36:10.800
But again, it's an entry point, right?
00:36:11.700 --> 00:36:14.080
So in Tonio, for example, yes, you have Tonio run.
00:36:14.210 --> 00:36:18.120
You want to instruct everything and configure the runtime and do whatever you want.
00:36:18.180 --> 00:36:19.340
Yes, you can do that.
00:36:19.550 --> 00:36:20.800
You don't want to do that.
00:36:21.060 --> 00:36:23.600
You have a single decorator, which is Tonio main.
00:36:24.020 --> 00:36:30.780
You put it on your function, your entry point in your program, and it's done.
00:36:32.500 --> 00:36:37.300
Yeah. All right. Before we talk about that, before we get too much in the weeds of it,
00:36:37.400 --> 00:36:43.480
why don't you just give us like, talk us through writing a program in this. There's also two ways,
00:36:43.580 --> 00:36:47.920
like maybe you could, after you talk about this, you could like sort of get into the,
00:36:48.380 --> 00:36:55.600
what this concept of colored functions versus not, and sometimes call async code viral code. And like,
00:36:55.680 --> 00:37:02.360
let's just like talk through the differences here. Yeah. So, okay. Yes. Tonyo has two
00:37:02.360 --> 00:37:06.940
supports two different syntax modes.
00:37:08.030 --> 00:37:11.960
And that's because, again, when I designed this,
00:37:12.580 --> 00:37:15.180
given that I just throw everything away
00:37:15.670 --> 00:37:16.620
and started from scratch,
00:37:17.290 --> 00:37:18.820
I ended up having a system
00:37:19.150 --> 00:37:23.580
which didn't really require async await syntax
00:37:23.730 --> 00:37:26.440
or a notation because at the end of the day,
00:37:26.550 --> 00:37:27.760
behind async await notation,
00:37:28.060 --> 00:37:29.740
those are generated, right?
00:37:30.740 --> 00:37:45.840
And so given the effort to support like two different syntax was like minimal, writing everything from scratch, I ended up like leaving up to the final user to decide, hey, you dislike async await temptation?
00:37:46.460 --> 00:37:55.400
There's still plenty of people that like, I don't know why, but they argue like all the time about, oh, you know, like I have to put async everywhere and a wait everywhere, whatever.
00:37:56.180 --> 00:37:57.560
OK, you don't like that.
00:37:58.000 --> 00:37:58.320
Guess what?
00:37:58.640 --> 00:38:00.980
Tonya also have just a yield syntax.
00:38:01.200 --> 00:38:10.240
So instead of await, you just yield from the next coroutine you want to suspend for or wait for.
00:38:11.320 --> 00:38:11.800
And that's it.
00:38:11.800 --> 00:38:15.460
You don't need to write async def everywhere.
00:38:17.480 --> 00:38:19.960
To be here is still colored, right?
00:38:20.140 --> 00:38:26.020
Because the moment you have a generator function, then whatever it calls it before has to be a generator.
00:38:26.080 --> 00:38:33.060
as well. But I guess my point was mainly like, okay, to support the syntax doesn't really take
00:38:33.210 --> 00:38:40.160
that much of a work. And once it was settled in, I mean, it's there and people can decide by,
00:38:40.580 --> 00:38:48.120
hey, you dislike one notation? Sure, just use the other one. But yes, let's say for people
00:38:48.580 --> 00:38:55.220
familiar with AsyncIO, the syntax, like the AsyncAwait syntax is very similar to AsyncIO.
00:38:55.900 --> 00:39:06.540
So you basically have your coroutine, so your sync dev, whatever you want, and you await primitives or stuff.
00:39:08.380 --> 00:39:20.800
So the main difference is that instead of importing stuff from a sync.io, so like, I don't know, sleep or timer or whatever, you import similar primitives from Tonio.
00:39:21.440 --> 00:39:28.080
We, of course, have way less primitives because, again, like, as I said before, AsyncIO has too many primitives.
00:39:29.380 --> 00:39:39.660
And the only big difference from AsyncIO, let's say, native people is that you have, like, spawn methods.
00:39:40.260 --> 00:39:41.879
So instead of saying...
00:39:43.460 --> 00:39:47.720
So in AsyncIO, when you want to make things,
00:39:48.200 --> 00:39:50.200
like spawn several tasks
00:39:50.640 --> 00:39:52.380
and then wait for all of them to complete,
00:39:52.680 --> 00:39:53.500
you usually do...
00:39:53.500 --> 00:39:56.080
You have different ways of doing this, right?
00:39:56.260 --> 00:39:58.620
You can have a task set.
00:39:59.240 --> 00:40:02.780
You can have scopes from Trio.
00:40:04.340 --> 00:40:06.060
But I mean, you can use gather
00:40:06.300 --> 00:40:08.980
so you can create tasks and then gather.
00:40:10.380 --> 00:40:10.840
with SyncIO.
00:40:10.920 --> 00:40:12.800
So Tonio is like,
00:40:12.980 --> 00:40:14.360
to do this is,
00:40:14.820 --> 00:40:16.420
you just have like two methods.
00:40:16.500 --> 00:40:17.160
You either spawn
00:40:19.000 --> 00:40:19.740
asynchronous stuff
00:40:19.980 --> 00:40:21.200
or you spawn something
00:40:21.200 --> 00:40:21.900
that is blocking
00:40:22.460 --> 00:40:23.480
and then the primitive
00:40:24.400 --> 00:40:25.920
is spawn blocking.
00:40:26.980 --> 00:40:28.140
The other major difference
00:40:28.400 --> 00:40:29.000
from a SyncIO
00:40:29.080 --> 00:40:30.680
is that when you spawn something
00:40:31.180 --> 00:40:33.000
that gets run immediately.
00:40:34.560 --> 00:40:35.940
Whereas like in a SyncIO
00:40:35.960 --> 00:40:37.080
this is true only
00:40:37.340 --> 00:40:38.440
if you create a task.
00:40:38.960 --> 00:40:41.380
Otherwise, everything else is eager.
00:40:41.960 --> 00:40:45.900
So in order to run whatever you want to wait,
00:40:45.980 --> 00:40:46.980
you need to await.
00:40:47.300 --> 00:40:49.600
So in AsyncIO, the vast majority of operations
00:40:50.540 --> 00:40:54.100
bound together the launching the operation
00:40:54.380 --> 00:40:55.440
and waiting for the result.
00:40:55.740 --> 00:40:58.320
Unless you create a task that starts immediately
00:40:58.720 --> 00:40:59.880
and then you await later, right?
00:41:00.820 --> 00:41:02.640
In Tonya, everything is wrapped around the spawn
00:41:02.840 --> 00:41:05.440
because you can call spawn and forget about it
00:41:05.660 --> 00:41:08.760
or you can await Tonya spawn to wait for the results, right?
00:41:08.900 --> 00:41:12.000
Or you can park the result of the spawn, the join, and join later.
00:41:12.980 --> 00:41:16.640
So that's, I don't know if, like, maybe it's more confusing than,
00:41:17.380 --> 00:41:19.860
it's hard to explain it simply.
00:41:20.260 --> 00:41:24.680
Maybe, but one of the challenges I've seen with standard asyncIO
00:41:24.780 --> 00:41:28.560
is you want to create a bunch of work and let it run,
00:41:28.820 --> 00:41:30.340
and then you want to get the answers back.
00:41:30.520 --> 00:41:33.140
So a naive way would be, like, call a bunch of stuff,
00:41:34.140 --> 00:41:35.980
and then when you call await,
00:41:36.380 --> 00:41:38.980
It's like you've got to create them not started
00:41:39.280 --> 00:41:40.440
and then start all of them
00:41:40.530 --> 00:41:42.500
and then go through each one and await them.
00:41:42.730 --> 00:41:44.520
Whereas this way, you get a list back
00:41:44.590 --> 00:41:45.680
and you just await them in order
00:41:45.990 --> 00:41:47.640
or await one after another somehow.
00:41:48.020 --> 00:41:49.380
They're already started, right?
00:41:49.470 --> 00:41:51.220
And so there's like the skip of this like,
00:41:51.330 --> 00:41:52.400
okay, I've got all the things
00:41:52.540 --> 00:41:53.540
that are going to become tasks,
00:41:53.660 --> 00:41:54.780
but I got to turn them into tasks
00:41:54.900 --> 00:41:56.180
so then I can await them
00:41:56.320 --> 00:41:57.400
because if I regularly await them,
00:41:57.520 --> 00:41:58.980
it'll still fall back to serial
00:41:59.500 --> 00:42:01.080
just running on the event loop, you know?
00:42:01.340 --> 00:42:01.440
Yeah.
00:42:01.730 --> 00:42:02.920
And I mean, like in Tanya is the same.
00:42:02.990 --> 00:42:04.260
Like if you await a coroutine,
00:42:04.370 --> 00:42:06.340
it means like I want to wait
00:42:06.360 --> 00:42:08.360
that to happen, right? If you don't want to
00:42:08.460 --> 00:42:10.300
just spawn, park
00:42:10.500 --> 00:42:12.220
the result of spawn into a
00:42:12.460 --> 00:42:14.220
variable, await later. I think
00:42:14.340 --> 00:42:15.120
there's an example
00:42:16.420 --> 00:42:17.480
down below in the page
00:42:19.240 --> 00:42:19.720
about
00:42:20.100 --> 00:42:21.960
spawning something and awaiting later.
00:42:24.520 --> 00:42:24.780
Somewhere.
00:42:26.220 --> 00:42:27.420
There's a bunch of documentation.
00:42:28.220 --> 00:42:28.960
Spawning there, yeah.
00:42:30.480 --> 00:42:31.760
But yeah, the idea
00:42:32.200 --> 00:42:34.000
is, and again, I didn't invent
00:42:34.040 --> 00:42:34.820
anything because
00:42:34.840 --> 00:42:41.240
the syntax. Yes, this is the example. So in this example, we compute like numbers,
00:42:41.550 --> 00:42:46.160
like stupid example, but it is to give the point. So we start like two functions,
00:42:46.850 --> 00:42:53.080
which computes numbers. Then we await for third function. So we immediately wait for the third
00:42:53.380 --> 00:43:00.260
result. And then we wait for the first two results. So in the meantime, so when you do the first poem,
00:43:00.660 --> 00:43:05.560
those two functions start already, like immediately at that point in your code,
00:43:06.660 --> 00:43:08.180
which kind of makes sense if you think about it,
00:43:08.230 --> 00:43:12.400
because it's like syntax-based, like you're invoking those coroutines, right?
00:43:12.980 --> 00:43:14.980
In that moment, you just don't await for them.
00:43:15.960 --> 00:43:20.820
And so at the end, like you just await for the parallel, actual parallel result.
00:43:21.220 --> 00:43:25.280
Like everything here is concurrent, but the only point in which you have parallel code,
00:43:25.580 --> 00:43:30.140
the actual parallel code is when you await for parallel, right?
00:43:30.920 --> 00:43:34.160
So that's the major difference from a Synco and Tonio.
00:43:35.000 --> 00:43:39.340
When you spawn stuff, that thing happened in parallel.
00:43:39.700 --> 00:43:43.320
So by default, Tonio starts with the number of threads
00:43:44.140 --> 00:43:46.340
equal to the number of your CPUs.
00:43:47.700 --> 00:43:52.980
The blocking, Tonio also has a blocking pool for blocking stuff.
00:43:54.360 --> 00:43:54.860
But that's separate.
00:43:54.900 --> 00:43:59.200
Let's say the main work loop that runs your code,
00:44:00.460 --> 00:44:04.280
by default, you have X threads per CPU cores.
00:44:05.380 --> 00:44:09.800
So if you run this on your M3 Max, M3 Studio Max,
00:44:10.740 --> 00:44:11.240
what was it?
00:44:11.600 --> 00:44:14.620
You will end up having 18 threads running stuff.
00:44:14.780 --> 00:44:14.920
Yeah.
00:44:15.360 --> 00:44:18.480
Which is kind of what we want in general, right?
00:44:19.300 --> 00:44:20.840
Like if I write a program-
00:44:20.840 --> 00:44:22.140
I'm sure you can configure it, right?
00:44:22.220 --> 00:44:24.140
You could configure the thread pool to say,
00:44:24.220 --> 00:44:26.580
like, look, this thing has to be a good citizen.
00:44:26.820 --> 00:44:28.220
Let's just take the number of CPUs,
00:44:28.320 --> 00:44:29.400
divide by two or something like that
00:44:29.400 --> 00:44:30.460
so I can still do other work.
00:44:30.680 --> 00:44:33.060
Yeah, you can like, again,
00:44:34.080 --> 00:44:36.700
that Tonyo main decorator
00:44:36.980 --> 00:44:38.360
or the Tonyo run method
00:44:40.840 --> 00:44:42.080
accepts some parameters
00:44:42.220 --> 00:44:43.680
so you can configure like the size
00:44:43.700 --> 00:44:45.220
of the standard thread pool,
00:44:45.640 --> 00:44:47.640
the maximum amount of blocking threads
00:44:47.840 --> 00:44:48.680
you want to spawn
00:44:49.280 --> 00:44:51.020
because that does get spawned on demand.
00:44:52.460 --> 00:44:58.660
you can configure if you want to have support for context bars because it's not that's another thing
00:44:58.940 --> 00:45:07.360
about python right like we kind of went from thread locals to context bars uh but in thonio
00:45:07.520 --> 00:45:12.840
that's a bit more complicated right because you have async code in multiple threads yeah because
00:45:12.930 --> 00:45:18.340
they used to leverage the thread right yeah so if you want to actually use context bars
00:45:18.360 --> 00:45:20.900
you have to tell to the runtime, right?
00:45:20.980 --> 00:45:24.360
Because otherwise, like, some side effects might be weird
00:45:24.940 --> 00:45:25.560
in that condition.
00:45:26.620 --> 00:45:26.960
Hold on.
00:45:28.319 --> 00:45:30.240
Nomenclature definition, please, here.
00:45:30.500 --> 00:45:32.160
What are context vars for people who don't know?
00:45:32.260 --> 00:45:32.720
Give us examples.
00:45:32.840 --> 00:45:33.140
Oh, yeah.
00:45:33.540 --> 00:45:37.940
So context vars, so the interpreter has these,
00:45:39.060 --> 00:45:43.280
every thread into the Python interpreter
00:45:43.580 --> 00:45:45.780
has what it's called a context.
00:45:47.140 --> 00:45:51.160
And so with a sync.io, so what was the problem?
00:45:51.500 --> 00:46:01.280
So we used to have thread locals, which meant like if I have two threads and I have, for example, a request,
00:46:01.660 --> 00:46:10.900
and I have to keep the request state global, like global between quotes in my code,
00:46:11.100 --> 00:46:16.900
but I want to use the correct request in the code
00:46:17.810 --> 00:46:21.480
and don't make them mix in the two different threads, states,
00:46:22.460 --> 00:46:26.800
we use thread vocals because that's basically like a dictionary,
00:46:27.210 --> 00:46:30.260
more or less, where you can store that data.
00:46:30.640 --> 00:46:34.200
And that snapshot of data is for a single thread.
00:46:35.060 --> 00:46:39.200
Context var are a similar concept, but for a same code,
00:46:39.340 --> 00:46:42.480
which means you can store global,
00:46:43.440 --> 00:46:44.560
again, between quotes,
00:46:44.900 --> 00:46:50.280
global state from an asynchronous code,
00:46:50.880 --> 00:46:52.880
suspend, and when you get back,
00:46:53.360 --> 00:46:55.780
you have the correct object back
00:46:56.620 --> 00:46:58.600
instead of mixing global state
00:46:58.880 --> 00:46:59.840
between different coroutines.
00:47:00.040 --> 00:47:01.420
Was my explanation good?
00:47:01.760 --> 00:47:02.280
Yeah, that's good.
00:47:02.900 --> 00:47:04.200
People are probably pretty common
00:47:04.380 --> 00:47:05.680
with flask.request.
00:47:05.860 --> 00:47:06.080
Yeah.
00:47:06.120 --> 00:47:07.940
Because you've got a view method
00:47:07.960 --> 00:47:10.500
or maybe deep down inside some lower part of your program,
00:47:10.720 --> 00:47:12.300
you're like, well, I need to know what the URL was.
00:47:12.300 --> 00:47:14.260
You just say Flask.request, you don't pass it around.
00:47:14.460 --> 00:47:17.080
Like very handy, probably architecturally a bad choice.
00:47:17.960 --> 00:47:20.080
You know, it's hard to like test it out, right?
00:47:20.200 --> 00:47:20.700
And you mock it.
00:47:20.720 --> 00:47:21.140
I don't know.
00:47:21.640 --> 00:47:22.660
Emmet does the same.
00:47:23.860 --> 00:47:25.900
It's the one thing I copied from Flask
00:47:26.200 --> 00:47:27.100
because I really like it.
00:47:27.960 --> 00:47:30.880
But I think we can do the same argument about database, right?
00:47:31.060 --> 00:47:32.980
Like with a synchronous database,
00:47:33.140 --> 00:47:35.340
like you have to start a context,
00:47:35.740 --> 00:47:40.860
Like, async with database, whatever, and then you need to pass that, like, all through.
00:47:41.340 --> 00:47:43.440
Like, a context bar is more handy.
00:47:43.840 --> 00:47:44.260
I don't know.
00:47:44.960 --> 00:47:45.340
Yeah, yeah.
00:47:45.380 --> 00:47:47.880
It certainly unlocks some really interesting things.
00:47:48.020 --> 00:47:50.080
Some extensions do cool things with it.
00:47:50.340 --> 00:47:51.300
So that's what you're talking about.
00:47:51.440 --> 00:47:54.960
But because it's not all just tied to the thread and using asyncio context variables,
00:47:55.220 --> 00:47:57.260
now it's kind of shared in potential ways.
00:47:57.540 --> 00:47:59.840
It's a little bit, you got to opt into it.
00:47:59.940 --> 00:48:00.200
That's right?
00:48:00.440 --> 00:48:01.120
Yeah, CSR.
00:48:01.340 --> 00:48:03.560
Again, like, it's a Boolean when you want to start the runtime.
00:48:05.100 --> 00:48:08.740
And I mean, it's an implementation detail.
00:48:09.860 --> 00:48:11.380
So it is just to be like,
00:48:11.700 --> 00:48:13.780
I think the big warning at the beginning
00:48:14.400 --> 00:48:15.040
should have been like,
00:48:15.300 --> 00:48:16.580
Tonyo is very alpha right now.
00:48:18.000 --> 00:48:20.520
So maybe that decision will change in the future
00:48:20.680 --> 00:48:22.820
because I think right now the default is false.
00:48:23.540 --> 00:48:24.940
That might become true
00:48:25.410 --> 00:48:27.580
as soon as I stabilize the API.
00:48:28.420 --> 00:48:29.480
But yeah, the idea was
00:48:29.880 --> 00:48:32.080
if you're testing Tonyo right now,
00:48:32.560 --> 00:48:34.540
if you're using Tonyo to do some tests right now,
00:48:34.880 --> 00:48:41.560
I want the developer to explicitly state, okay, I'm going to use context bars.
00:48:41.960 --> 00:48:48.280
Because again, the fact you have multiple threads and stuff happening in parallel,
00:48:49.200 --> 00:48:55.300
I just want for the developer to be sure if it's thinking model is correct
00:48:55.460 --> 00:48:57.820
before trying to do some stuff.
00:48:59.160 --> 00:49:01.060
Because again, Tony is multi-threaded.
00:49:01.160 --> 00:49:07.900
multi-threaders is usually like not it's it's i don't know i think for humans multi-threading is
00:49:08.200 --> 00:49:16.520
hard to get sometimes um so that's the trade-off right like uh yes we now have all of these
00:49:16.700 --> 00:49:22.480
capabilities on the other end we need to think about the fact that the sync io always had this
00:49:22.940 --> 00:49:30.420
uh hidden uh let's say a feature which is hey there's only one coroutine running at the time
00:49:30.620 --> 00:49:32.640
Whereas Antonio can have like whatever number.
00:49:32.940 --> 00:49:33.100
Yep.
00:49:34.160 --> 00:49:37.160
Now you still had to manage stuff across await calls.
00:49:37.760 --> 00:49:43.520
So that was, I feel like people felt like the GIL saved them from all thread considerations,
00:49:43.960 --> 00:49:46.200
like locks and semaphores and so on.
00:49:46.460 --> 00:49:47.360
And I don't think it did.
00:49:47.640 --> 00:49:50.780
There's not a guarantee that like a thread couldn't interrupt you.
00:49:50.880 --> 00:49:52.780
It's just less likely to interrupt you, right?
00:49:53.080 --> 00:49:59.600
So like the simple thing is that it did around other primitives.
00:49:59.940 --> 00:50:08.680
So for example, in a SyncIO, it's really hard to deadlock yourself because you're using like a threading lock.
00:50:09.000 --> 00:50:16.000
Because again, like the hidden feature of a SyncIO is you have only one thread working on that shit, right?
00:50:16.740 --> 00:50:17.320
Yeah, exactly.
00:50:17.740 --> 00:50:19.080
Like you can do all the locks you want.
00:50:19.160 --> 00:50:20.600
It's the same thread in the re-entrance.
00:50:21.140 --> 00:50:21.380
Exactly.
00:50:21.920 --> 00:50:24.500
It's effectively a no-op other than it costs CPU.
00:50:24.920 --> 00:50:25.640
Yes, exactly.
00:50:25.860 --> 00:50:38.300
Whereas in Tonio, like that's exactly where people usually make mistakes because now you have the Tonio locking because you have the asynchronous locks, but you also have thread locks, which are two different things.
00:50:39.000 --> 00:50:42.800
And if you, and so like you can deadlock Tonio.
00:50:43.100 --> 00:50:43.300
How?
00:50:43.580 --> 00:50:43.700
Yeah.
00:50:44.020 --> 00:50:44.940
Welcome to multi-threading.
00:50:45.280 --> 00:50:45.480
Yes.
00:50:45.660 --> 00:50:52.160
Like you, you, you, you create, like you enter a threading lock and then you await a coroutine inside the threading lock.
00:50:52.310 --> 00:50:53.320
Now you're deadlocked.
00:50:53.580 --> 00:50:53.680
Yeah.
00:50:53.780 --> 00:50:55.860
Because the two things doesn't speak each other.
00:50:56.960 --> 00:50:59.700
But I guess my advice,
00:51:00.060 --> 00:51:03.360
if people want to start working in multi-threaded Python,
00:51:03.540 --> 00:51:08.620
my advice is just think about those two rules
00:51:09.140 --> 00:51:10.860
and then multi-threading is not that hard.
00:51:11.080 --> 00:51:12.380
Again, rule number one,
00:51:13.060 --> 00:51:16.800
never await inside a threading lock.
00:51:17.180 --> 00:51:17.960
That's a simple rule.
00:51:18.660 --> 00:51:21.740
Also because in Python, you use width lock.
00:51:22.240 --> 00:51:25.400
So that's super easy even to identify, right?
00:51:26.340 --> 00:51:27.620
That's rule number one.
00:51:28.120 --> 00:51:30.160
Rule number two, you have multiple threads.
00:51:30.510 --> 00:51:32.700
So if you want to write into anything,
00:51:33.760 --> 00:51:37.220
I mean, all the objects are thread safe in free thread of Python
00:51:37.420 --> 00:51:40.560
because a dictionary is thread safe, a list is thread safe, whatever.
00:51:41.080 --> 00:51:45.840
But that doesn't mean like you can screw up in using those.
00:51:46.020 --> 00:51:48.780
Because if you write state anywhere,
00:51:49.400 --> 00:51:58.560
You might have like race conditions and side effects because you expect to write something and then expect to read that value.
00:51:58.800 --> 00:52:01.380
But in the meantime, you have another thread right into the same location.
00:52:01.680 --> 00:52:08.180
So again, like my advice is rule number one, never use locks, threading locks, and then await.
00:52:08.580 --> 00:52:12.560
If you want to have an async lock, then use an async lock.
00:52:12.560 --> 00:52:13.880
So a Tonio lock for that.
00:52:14.720 --> 00:52:16.620
Rule number two, watch out.
00:52:16.820 --> 00:52:25.340
So you probably need locks when you want to be sure that, like, when you have a sequence of operation, right, on shared memory.
00:52:25.740 --> 00:52:26.880
Those are the two rules.
00:52:27.720 --> 00:52:31.740
I think with those two rules, multithreading is not that hard.
00:52:31.960 --> 00:52:32.440
Yeah, agreed.
00:52:32.660 --> 00:52:38.560
So does Tonio come with its own dedicated lock and synchronization primitives?
00:52:38.900 --> 00:52:41.460
Yes, there's an entire sync module.
00:52:41.980 --> 00:52:45.600
So Tonio has different modules.
00:52:46.580 --> 00:52:51.920
Tonya time, which contains time primitives, like timeouts, timers, etc.
00:52:52.300 --> 00:52:56.400
It has async module, which contains all the synchronization primitives,
00:52:56.580 --> 00:52:59.520
so logs, semaphore, barriers, etc.
00:53:01.000 --> 00:53:06.380
It has the network module, which is one providing like sockets.
00:53:06.960 --> 00:53:10.580
So that's quite a big difference from a sync.io.
00:53:10.580 --> 00:53:16.080
So in a sync.io, we don't have like an async socket library.
00:53:16.440 --> 00:53:20.620
So if you think about the standard lib socket module,
00:53:21.359 --> 00:53:25.480
Tonio, AsyncIO needs you to use that
00:53:25.760 --> 00:53:28.280
and then pass the sockets to the event loop
00:53:28.560 --> 00:53:30.320
and then handle the socket through the event.
00:53:31.200 --> 00:53:34.360
Whereas in Tonio, there's a network module
00:53:34.540 --> 00:53:36.340
and inside that module, there's a socket module,
00:53:36.670 --> 00:53:40.380
which has the same exact interface of the standard lib plus socket.
00:53:40.960 --> 00:53:42.040
The only difference is, of course,
00:53:42.600 --> 00:53:45.500
every async method is async in Tonio.
00:53:47.280 --> 00:53:47.640
there's the
00:53:48.860 --> 00:53:50.140
streams module
00:53:50.240 --> 00:53:52.140
inside the network module which is the high level
00:53:53.120 --> 00:53:53.900
trio like
00:53:54.580 --> 00:53:55.740
interface to
00:53:56.160 --> 00:53:57.440
make it easy to manage
00:53:57.980 --> 00:54:00.020
network. It has a file
00:54:00.140 --> 00:54:02.020
system module which
00:54:02.280 --> 00:54:02.640
contains
00:54:03.980 --> 00:54:04.640
all the standard
00:54:06.440 --> 00:54:07.880
open kind of
00:54:08.220 --> 00:54:09.060
methods that they
00:54:09.240 --> 00:54:10.300
async with open
00:54:10.860 --> 00:54:12.940
tonio.files.open something like that
00:54:13.100 --> 00:54:14.539
yeah.fs yes
00:54:15.260 --> 00:54:16.500
and it also contains like
00:54:17.400 --> 00:54:18.720
mirror of the
00:54:19.080 --> 00:54:20.260
pathlib, standard lib
00:54:20.680 --> 00:54:22.100
pathlib, so you can use like
00:54:22.400 --> 00:54:24.780
frontoniofs import path and that path
00:54:24.940 --> 00:54:26.060
has all the methods
00:54:26.700 --> 00:54:28.280
that are required to be asynchronous
00:54:29.840 --> 00:54:30.860
so that's
00:54:31.020 --> 00:54:32.120
more or less like the
00:54:33.000 --> 00:54:33.640
design
00:54:34.860 --> 00:54:36.260
I don't think like
00:54:36.420 --> 00:54:38.200
so right now I'm at
00:54:38.360 --> 00:54:40.680
tonio 0.9.14
00:54:40.960 --> 00:54:41.440
I guess
00:54:42.580 --> 00:54:44.520
and I don't think
00:54:44.540 --> 00:54:48.660
There's much left in terms of modules and primitives and features.
00:54:50.440 --> 00:54:52.280
The big feature missing is Windows support.
00:54:52.740 --> 00:54:53.440
More on that later.
00:54:55.839 --> 00:55:02.020
But yeah, I'd say I'm close to stabilize the API.
00:55:02.380 --> 00:55:04.440
So right now, again, this is all alpha.
00:55:05.040 --> 00:55:07.780
That's mostly because there's only me working on this.
00:55:08.920 --> 00:55:12.300
And I have Fable working on a bunch of other things,
00:55:12.920 --> 00:55:16.480
which I guess like if I can digress for a second,
00:55:17.040 --> 00:55:20.420
I think like I have a very peculiar and unique way of using AI
00:55:20.840 --> 00:55:22.020
because right now I'm like,
00:55:22.780 --> 00:55:26.080
and I guess like it's a good use case to advertise.
00:55:26.420 --> 00:55:28.180
So the entirety of Tonio,
00:55:28.340 --> 00:55:32.160
so Tonio has maybe 1% AI written code.
00:55:32.380 --> 00:55:35.900
The only code that was written by Claude
00:55:36.780 --> 00:55:38.140
was the pytest plugin.
00:55:38.540 --> 00:55:40.520
Oh, by the way, we also have a pytest plugin.
00:55:40.780 --> 00:55:42.760
so you can hide the smart tone.
00:55:45.200 --> 00:55:48.220
But that's the only part that I let AI touch.
00:55:49.640 --> 00:55:51.800
Everything else, like it's old school.
00:55:52.210 --> 00:55:56.780
I written all of it like the old way, text editor,
00:55:57.440 --> 00:56:01.280
not even any suggestion and everything.
00:56:02.420 --> 00:56:07.260
What I used AI for, and it helped me a lot
00:56:07.520 --> 00:56:12.660
into reaching a state in which I think Tonio is really stable now
00:56:13.520 --> 00:56:18.480
was to use AI to build projects that were using Tonio
00:56:18.860 --> 00:56:21.440
and stress test everything about Tonio.
00:56:21.510 --> 00:56:24.400
So for example, I made a port of Pi,
00:56:25.570 --> 00:56:27.520
the harness from Maria Zeckner.
00:56:28.510 --> 00:56:31.440
So I made Claude rewrite the whole thing in Python
00:56:32.200 --> 00:56:35.900
because Pi is written in TypeScript, regardless of the name.
00:56:37.320 --> 00:56:40.960
So I made that entire harness with Claude saying,
00:56:41.030 --> 00:56:42.020
okay, take five,
00:56:42.660 --> 00:56:46.380
meter every behavior and just use Tonio to do everything.
00:56:47.430 --> 00:56:49.200
And that like helped me like catch
00:56:49.510 --> 00:56:52.740
like probably 95% of the bugs in Tonio.
00:56:53.720 --> 00:56:55.920
And I kind of started doing the same for the ecosystem.
00:56:57.100 --> 00:56:59.320
Maybe we can talk about it a bit later,
00:56:59.740 --> 00:57:01.760
but yeah, that's also like a super,
00:57:02.480 --> 00:57:03.940
it's super useful for me, right?
00:57:04.080 --> 00:57:06.240
Like as a solo open source developer,
00:57:06.620 --> 00:57:08.660
which, yeah, I want to focus on Tonio.
00:57:09.080 --> 00:57:11.500
I don't have time to do all the other packages.
00:57:12.040 --> 00:57:13.320
And so, yeah, that was nice.
00:57:13.440 --> 00:57:14.760
That's a very interesting way.
00:57:14.760 --> 00:57:16.080
I hadn't really considered that.
00:57:16.300 --> 00:57:19.360
It's just like, I need users before I have users.
00:57:19.840 --> 00:57:21.840
I need these three use cases covered.
00:57:21.980 --> 00:57:24.240
So give me a web app that uses an async database.
00:57:24.500 --> 00:57:27.180
Give me this terminal app and so on.
00:57:27.180 --> 00:57:28.360
And that's, yeah, that's really cool.
00:57:28.720 --> 00:57:28.920
Yeah.
00:57:29.660 --> 00:57:29.800
Okay.
00:57:30.180 --> 00:57:30.620
I agree.
00:57:30.820 --> 00:57:31.700
Ecosystem is interesting.
00:57:31.780 --> 00:57:32.660
I want to talk about it.
00:57:32.840 --> 00:57:34.940
But before that, I want to come back to blocking threads.
00:57:35.420 --> 00:57:35.680
Yes.
00:57:35.960 --> 00:57:39.600
So blogging threads are CPU bound generally type of things.
00:57:39.610 --> 00:57:40.060
Is that right?
00:57:40.580 --> 00:57:44.680
So I think Tonio is a bit peculiar in this regard
00:57:45.120 --> 00:57:47.680
because it really depends what your program does.
00:57:48.160 --> 00:57:54.020
Because if your program, whatever that is, has mixed load,
00:57:54.210 --> 00:57:58.180
which means like you have both IO bound stuff,
00:57:58.700 --> 00:58:03.960
like network, disk, terminal, whatever, and CPU bound stuff,
00:58:05.180 --> 00:58:10.460
then the only way to be sure that everything keeps running smoothly,
00:58:10.820 --> 00:58:13.300
like on the network part, the IO part,
00:58:13.620 --> 00:58:18.840
is to spawn the CPU bound stuff on the blocking thread pool.
00:58:19.460 --> 00:58:24.740
But if your program is just CPU bound, then who cares?
00:58:24.990 --> 00:58:28.580
Just use the normal thread pool
00:58:29.710 --> 00:58:31.700
because then you don't have anything to block.
00:58:32.000 --> 00:58:34.360
like your CPU bound, your CPU limited.
00:58:35.280 --> 00:58:37.340
So the standard configuration of Tonio
00:58:37.800 --> 00:58:40.760
gives you exactly the perfect number of CPU cores to use.
00:58:41.860 --> 00:58:45.520
On the other hand, if you just have like IO bound.
00:58:46.100 --> 00:58:48.920
So again, like if you have just CPU bound work
00:58:49.100 --> 00:58:50.440
or just IO bound work,
00:58:50.500 --> 00:58:52.540
you can use like the standard pool,
00:58:53.860 --> 00:58:55.200
nothing particular like this.
00:58:55.440 --> 00:58:57.140
The blocking thread pool is useful
00:58:57.440 --> 00:58:59.200
only when you have mixed workloads
00:58:59.820 --> 00:59:01.700
and you need to balance them, right?
00:59:01.960 --> 00:59:03.820
Okay, how do I create such a thing?
00:59:04.160 --> 00:59:07.440
How do I start a task in one or the other?
00:59:07.980 --> 00:59:11.060
So every await or yell destruction
00:59:11.700 --> 00:59:12.780
or a tonio spawn destruction
00:59:13.480 --> 00:59:15.600
runs on the standard thread pool.
00:59:16.660 --> 00:59:19.280
To spawn something on the blocking thread pool
00:59:19.440 --> 00:59:20.860
is just tonio spawn blocking.
00:59:21.190 --> 00:59:21.300
Done.
00:59:21.670 --> 00:59:23.640
You don't need to manage the size of the pool.
00:59:23.690 --> 00:59:26.140
You don't need to create the pool.
00:59:27.480 --> 00:59:32.700
If you want to control concurrency in the blocking threat pool,
00:59:34.240 --> 00:59:37.200
you either use, like, barriers when you spawn stuff
00:59:38.320 --> 00:59:40.260
or semaphores when you spawn stuff.
00:59:41.700 --> 00:59:42.820
But, yeah, like, in general,
00:59:43.300 --> 00:59:45.740
it's not different from spawning anything else.
00:59:46.160 --> 00:59:46.340
Sure.
00:59:46.620 --> 00:59:47.640
So you can spawn blocking.
00:59:48.540 --> 00:59:49.640
Here's what I was thinking.
00:59:49.800 --> 00:59:50.620
Here's what I was getting at.
00:59:51.440 --> 00:59:53.540
That I think would be a sweet feature.
00:59:53.980 --> 00:59:57.100
You've got at tonio.main for the entry point, right?
00:59:57.560 --> 00:59:57.700
Yes.
00:59:58.260 --> 01:00:01.640
So that runs on tonio.run instead of just Python run or whatever.
01:00:02.000 --> 01:00:05.200
For functions that you know are computational,
01:00:05.440 --> 01:00:08.140
you would like to ensure that they run on a blocking thread
01:00:08.640 --> 01:00:09.820
or along those lines.
01:00:10.120 --> 01:00:15.540
You could put a decorator that just says tonio.cpu or whatever.
01:00:15.640 --> 01:00:16.000
I don't know.
01:00:16.120 --> 01:00:19.000
You come up with a name, but that way I don't even have to think.
01:00:19.400 --> 01:00:21.840
If I import another library also built on tonio,
01:00:22.040 --> 01:00:25.020
I don't know, oh, that function wants me to run it this way.
01:00:25.490 --> 01:00:26.480
No, I just call it.
01:00:26.640 --> 01:00:29.600
Antonio goes, ah, this is an async CPU one.
01:00:29.910 --> 01:00:33.680
So we'll scale it up, scaffold it up on that, basically.
01:00:33.960 --> 01:00:36.200
That's a nice suggestion, okay?
01:00:36.940 --> 01:00:37.200
All right.
01:00:37.320 --> 01:00:40.200
I think it will land in Antonio 0.10, yes.
01:00:40.580 --> 01:00:41.300
Awesome, okay, cool.
01:00:41.680 --> 01:00:42.380
I like that.
01:00:43.260 --> 01:00:46.720
When you're in the function, the locality of the information
01:00:46.790 --> 01:00:49.080
that, oh, this is computational and so on is right there.
01:00:49.170 --> 01:00:50.920
But when you're calling, and especially in a big app,
01:00:51.090 --> 01:00:51.700
you don't know, right?
01:00:51.780 --> 01:00:52.680
So I think that would be really cool.
01:00:53.060 --> 01:00:53.880
So I'm glad you like it.
01:00:54.060 --> 01:00:55.080
No, we'll work on that.
01:00:55.640 --> 01:00:55.940
Awesome.
01:00:56.940 --> 01:00:58.620
All right, we got time for two really quick things.
01:00:59.040 --> 01:01:02.100
So one thing that is nice about standard asyncio
01:01:02.280 --> 01:01:03.640
is everyone uses asyncio
01:01:03.840 --> 01:01:05.460
if they're writing async code at the moment,
01:01:05.930 --> 01:01:06.840
unless they do something like Trio
01:01:06.970 --> 01:01:07.680
or one of these other things.
01:01:08.060 --> 01:01:11.760
So for example, if I use HTTPX2 or HTTPX
01:01:12.300 --> 01:01:14.520
and I, with async, you know,
01:01:14.840 --> 01:01:16.440
async with create a client of it
01:01:16.500 --> 01:01:17.520
and I await its get,
01:01:18.260 --> 01:01:20.180
that runs on the same sort of deal.
01:01:20.440 --> 01:01:23.040
but it's probably not compatible with Tonio, is it?
01:01:23.270 --> 01:01:24.340
And if it's not, what do I do?
01:01:24.940 --> 01:01:25.760
Yeah, it's not.
01:01:28.580 --> 01:01:32.080
So I have one thing that is the current state,
01:01:32.380 --> 01:01:35.580
and I also have the idea for the future.
01:01:35.850 --> 01:01:40.060
So right now we have a package which is called TonioMonkey,
01:01:40.460 --> 01:01:43.140
and you probably can guess what it means, what it does.
01:01:43.820 --> 01:01:48.180
It basically like monkey patches popular,
01:01:48.860 --> 01:01:55.520
Or I would either say, like, asyncio native libraries I thought about in the last few months.
01:01:56.580 --> 01:01:57.480
That might be useful.
01:01:59.320 --> 01:02:04.980
And so I guess today we have, like, TonyaMonkey can monkey patch async.
01:02:05.320 --> 01:02:05.860
No, sorry.
01:02:06.640 --> 01:02:07.200
PsychoPG.
01:02:08.380 --> 01:02:11.200
It can patch HTTPX and HTTPX2.
01:02:12.200 --> 01:02:13.760
It can patch Redis.
01:02:15.220 --> 01:02:17.240
And I think that's most of it for the moment.
01:02:17.760 --> 01:02:21.040
But I'm open for issues.
01:02:21.360 --> 01:02:28.440
If anyone from the listeners want to try Tonio to an existing code base
01:02:28.680 --> 01:02:32.640
and you have a package you would like to see monkey patched,
01:02:33.180 --> 01:02:35.340
just open an issue in the TonioMonkey repo
01:02:35.900 --> 01:02:39.900
or even in Tonio discussions, and I will look at it.
01:02:41.560 --> 01:02:46.860
Because again, right now I need the vast majority of possible use cases
01:02:46.880 --> 01:02:48.480
to see that I covered everything.
01:02:50.140 --> 01:02:52.460
Ideally, as soon as I stabilize the API
01:02:52.680 --> 01:02:55.040
and I say, okay, this is the thing,
01:02:56.200 --> 01:03:03.380
my idea was to try to contact the NEIO maintainers
01:03:04.900 --> 01:03:13.260
to see if we can have NEIO backend in NEIO,
01:03:13.680 --> 01:03:19.820
which should be like a way easier way to deal with this.
01:03:20.900 --> 01:03:22.720
I'm not sure how much it's feasible because again,
01:03:22.900 --> 01:03:27.540
like the main problem for Tonio right now is that it doesn't support any of the
01:03:27.740 --> 01:03:28.600
synchio primitives.
01:03:28.880 --> 01:03:33.260
So if any code base like relies a ton on task or futures or whatever,
01:03:34.660 --> 01:03:36.520
that's not in the runtime.
01:03:36.940 --> 01:03:37.060
Yeah.
01:03:37.300 --> 01:03:37.420
Yeah.
01:03:37.760 --> 01:03:37.880
Okay.
01:03:38.400 --> 01:03:38.640
Interesting.
01:03:38.980 --> 01:03:39.140
Interesting.
01:03:39.340 --> 01:03:40.560
But there are some choices.
01:03:40.820 --> 01:03:42.580
And are you also creating your own?
01:03:42.740 --> 01:03:48.320
I saw some custom HTTP or some other library or two.
01:03:48.980 --> 01:03:53.640
I'm also trying to be a very contained ecosystem of libraries,
01:03:54.120 --> 01:03:55.460
mainly on the web part.
01:03:55.660 --> 01:04:01.580
So right now, I published a low-level HTTP library for Python
01:04:02.430 --> 01:04:06.440
that is compatible both with Asyncio and Tonio.
01:04:07.040 --> 01:04:08.680
It's called HTTPunk.
01:04:09.100 --> 01:04:17.740
I published an eye-level client, HTTP client, on top of that, which is called Punk Rec.
01:04:19.360 --> 01:04:27.020
So again, you can use that regardless of Tonio Asincaio, because the adaptation layer is inside the package.
01:04:27.580 --> 01:04:38.340
I kind of published a very beta experiment of a Uvicorn port to work in Tonio,
01:04:38.680 --> 01:04:45.120
Which I guess brings up a question like, hey, when will Granian support this?
01:04:45.430 --> 01:04:45.960
Am I right?
01:04:46.220 --> 01:04:46.560
Yeah, right.
01:04:47.640 --> 01:04:48.720
So I have plans.
01:04:50.080 --> 01:04:52.700
It's actually way bigger plans for Granian.
01:04:53.820 --> 01:04:58.700
So as I said before, 2026 was a bit boring in Granian.
01:04:58.900 --> 01:05:02.340
Like, no, a bunch of new features, not a bunch of that stuff.
01:05:02.390 --> 01:05:08.140
But that's just because several months ago, I started designing two things for Granian.
01:05:08.280 --> 01:05:13.140
So the first is this revision two of the ArchGee protocol,
01:05:13.680 --> 01:05:17.020
which will be async independent.
01:05:17.600 --> 01:05:20.580
So it will become like a callback protocol
01:05:21.780 --> 01:05:25.720
because the other big feature I want to add in Granian
01:05:26.180 --> 01:05:29.320
is to support a bunch of runtimes.
01:05:29.660 --> 01:05:31.620
So my idea is not the Granian tree,
01:05:32.240 --> 01:05:36.040
when it will land, no promises on the time here,
01:05:36.380 --> 01:05:37.640
but like when it will land,
01:05:37.980 --> 01:05:45.700
Ideally, it will support Asyncio, Trio, Tonio, gEvent, eventlet, and a bunch of stuff.
01:05:45.890 --> 01:05:55.360
So that's the idea, is to isolate, let's say, the protocol implementation and the Python runtime implementation.
01:05:55.620 --> 01:05:57.540
So it will happen one day.
01:05:59.660 --> 01:06:04.620
In the meantime, I wanted to have a test server to use with Tonio and see what happens.
01:06:04.890 --> 01:06:05.640
So that's why.
01:06:07.220 --> 01:06:08.420
Yeah, using something like
01:06:08.780 --> 01:06:10.860
Cord or FastAPI or something like that.
01:06:11.120 --> 01:06:12.900
If you were able to run it in Granian
01:06:13.400 --> 01:06:15.020
with the Tonio, Tonio
01:06:15.520 --> 01:06:17.220
backend, the async
01:06:17.560 --> 01:06:18.700
endpoints you write would be
01:06:19.120 --> 01:06:19.960
running on Tonio, yeah?
01:06:20.240 --> 01:06:21.360
Yeah, that's the plan.
01:06:22.800 --> 01:06:25.340
In TonioMonkey, there are also patches for FastAPI,
01:06:25.480 --> 01:06:26.820
by the way. And I'm working
01:06:27.060 --> 01:06:28.480
on a one on Django.
01:06:28.920 --> 01:06:30.760
I need to ping
01:06:30.900 --> 01:06:31.900
Carleton on that
01:06:32.820 --> 01:06:34.380
because we met this year
01:06:34.420 --> 01:06:36.400
at this year, PyCon Italy,
01:06:36.940 --> 01:06:38.040
and we talk about it.
01:06:39.259 --> 01:06:42.240
So maybe in a month or so,
01:06:42.660 --> 01:06:44.440
we will also have like a patch for Django.
01:06:44.720 --> 01:06:44.880
Sweet.
01:06:45.190 --> 01:06:51.360
Yeah, I just had Carlton on to talk about
01:06:52.260 --> 01:06:55.700
basically all the async work in Django 6 and 6.0 and so on.
01:06:55.730 --> 01:06:56.360
So that was fun.
01:06:56.400 --> 01:06:58.600
I watched the episode and I was like,
01:06:58.900 --> 01:07:01.420
huh, sounds familiar, huh, huh.
01:07:01.960 --> 01:07:02.980
Yeah, we've talked about that.
01:07:03.030 --> 01:07:03.380
I see.
01:07:03.820 --> 01:07:04.060
Interesting.
01:07:04.480 --> 01:07:04.660
All right.
01:07:04.720 --> 01:07:07.520
Well, I got one final main topic for us.
01:07:07.860 --> 01:07:08.020
Yep.
01:07:08.280 --> 01:07:08.680
Oh, yeah.
01:07:08.720 --> 01:07:10.200
What about windows?
01:07:12.260 --> 01:07:12.520
So.
01:07:12.800 --> 01:07:15.180
And I'm not putting pressure on you to do it.
01:07:15.260 --> 01:07:15.700
No, no, no.
01:07:16.060 --> 01:07:17.560
I'm just rounding out the conversation.
01:07:18.040 --> 01:07:18.300
Okay.
01:07:18.660 --> 01:07:26.480
So if you ask me like a few months ago, my answer would probably be like that short video
01:07:26.920 --> 01:07:31.740
extract from a series like where you had the actor.
01:07:32.780 --> 01:07:32.940
Whatever.
01:07:33.080 --> 01:07:41.700
um you know i'm gonna not gonna not gonna swear on the podcast but uh uh you get what i mean
01:07:41.800 --> 01:07:48.640
uh so i i'm not so first of all like the usual problem so supporting windows in granian
01:07:49.180 --> 01:07:55.220
it's a pain man like a real pain like i'm not joking right because it has like all these
01:07:55.560 --> 01:08:02.500
quirk behaviors not just in python not just in terms of threads like do you know that win that
01:08:02.520 --> 01:08:11.220
that windows have as like a bug from i think it was like nt4 uh that like if you if you share a
01:08:11.430 --> 01:08:17.859
socket it becomes blocking and so you cannot use it like anywhere um so windows for start like
01:08:18.029 --> 01:08:26.299
windows is a pain and is a very weird operating system second i don't have like a windows environment
01:08:27.440 --> 01:08:29.120
because finally in 2026,
01:08:29.380 --> 01:08:32.200
I was able to finally have just macOS and Linux
01:08:32.580 --> 01:08:34.980
on all my machines, also for gaming.
01:08:35.319 --> 01:08:36.580
So I'm so happy.
01:08:37.960 --> 01:08:39.420
So those are, like I say,
01:08:39.759 --> 01:08:43.240
the two main preambles on Windows support.
01:08:44.499 --> 01:08:48.359
But also because implementing the library
01:08:48.400 --> 01:08:52.120
I use Antonio to manage the Polar,
01:08:52.339 --> 01:08:53.819
the actual event loop,
01:08:54.859 --> 01:08:58.220
It supports Windows, but you know, weird way.
01:08:58.580 --> 01:09:03.520
So, months ago I was like almost a hundred percent convinced now,
01:09:03.839 --> 01:09:08.799
never windows, never now, thanks to fable by chatting with fable.
01:09:09.240 --> 01:09:18.380
I think we found a way to hack into, into meal, this library to, to fake
01:09:18.779 --> 01:09:20.140
some stuff about windows.
01:09:20.220 --> 01:09:24.359
So it will interpret some stuff as TCP sockets.
01:09:24.460 --> 01:09:27.540
even if they are like file descriptors or anything.
01:09:28.140 --> 01:09:30.980
So I won't make a promise here
01:09:31.380 --> 01:09:33.900
because then I have to do it.
01:09:34.240 --> 01:09:37.759
But ideally, end of the year,
01:09:38.120 --> 01:09:41.020
we might see partial Windows support in Tom.
01:09:41.190 --> 01:09:41.319
Sweet.
01:09:41.640 --> 01:09:43.420
So that's the idea right now.
01:09:44.120 --> 01:09:47.640
I still hate Microsoft, to be clear here.
01:09:47.640 --> 01:09:49.740
Like, man, I just like Windows.
01:09:51.720 --> 01:09:54.340
So if any of the listeners,
01:09:54.450 --> 01:09:56.860
like if you're programming in Windows
01:09:57.560 --> 01:09:58.740
and not using Weasel,
01:09:59.160 --> 01:10:00.720
please put down in the comments why.
01:10:01.220 --> 01:10:04.960
Like, why are you self-inflicting this to you?
01:10:05.140 --> 01:10:07.440
Like, explain me the rationale
01:10:08.100 --> 01:10:11.180
into dealing with all the BS
01:10:11.480 --> 01:10:13.200
that Windows 11 is nowadays.
01:10:14.620 --> 01:10:15.580
So yeah, but anyways,
01:10:17.080 --> 01:10:18.440
I will try end of the year
01:10:18.440 --> 01:10:21.120
to have some form of Windows support in time.
01:10:21.700 --> 01:10:23.480
I can tell, I can sense your excitement.
01:10:23.880 --> 01:10:25.800
My Windows computer over there is Windows 10, by the way.
01:10:26.220 --> 01:10:27.520
But I do think for people listening,
01:10:28.240 --> 01:10:30.600
Windows Subsystem for Linux is an escape hatch
01:10:30.950 --> 01:10:31.860
that you can do, right?
01:10:32.020 --> 01:10:33.920
If you had a project, you really want to use this here,
01:10:34.100 --> 01:10:35.840
like set up Windows Subsystem for Linux
01:10:35.950 --> 01:10:36.620
and run it there, right?
01:10:36.840 --> 01:10:40.320
To be here, like the only reason I want to,
01:10:40.500 --> 01:10:42.680
like I want to try to add Windows support in Tonio
01:10:42.820 --> 01:10:43.920
is not for developers,
01:10:44.420 --> 01:10:47.880
is actually to provide developers a way to say,
01:10:48.050 --> 01:10:50.300
okay, I want to build a program in Python
01:10:50.300 --> 01:10:53.180
with Tonio that has to work like everywhere.
01:10:53.700 --> 01:10:54.240
Because right now, again,
01:10:54.440 --> 01:10:56.280
like if you code anything in Tonio,
01:10:56.700 --> 01:10:58.480
it works only on POSIX systems,
01:10:58.640 --> 01:10:59.860
so only Linux and macOS.
01:11:00.280 --> 01:11:02.100
So that's the reason why I want to have
01:11:02.300 --> 01:11:04.360
at least some form of Windows support
01:11:04.480 --> 01:11:06.620
because, I mean, it's not about developers.
01:11:07.340 --> 01:11:09.080
It's about like where the software runs
01:11:09.400 --> 01:11:09.940
at the end of the day.
01:11:10.100 --> 01:11:10.520
Right, right.
01:11:10.620 --> 01:11:12.180
It's the deployment targets, yeah.
01:11:12.560 --> 01:11:12.640
Yeah.
01:11:13.040 --> 01:11:14.360
All right, Joe, final call to action.
01:11:14.620 --> 01:11:15.460
People have been listening
01:11:15.560 --> 01:11:17.000
and they're like, this sounds pretty excellent.
01:11:17.600 --> 01:11:18.380
I want to try it.
01:11:18.740 --> 01:11:19.300
What do you tell them?
01:11:19.540 --> 01:11:27.760
I mean, like, again, I think we're living in the perfect moment to try stuff.
01:11:28.780 --> 01:11:33.900
We've given with this big opportunity, big new opportunity of free-threaded Python.
01:11:35.140 --> 01:11:36.960
We have AI agents.
01:11:38.500 --> 01:11:44.040
And by the way, I was surprised, but like pointing any agent, any model,
01:11:44.780 --> 01:11:48.620
frontier model to a project and say, okay, implement this in Tonio.
01:11:48.840 --> 01:11:49.480
I was surprised.
01:11:49.620 --> 01:11:52.480
They got all the API correctly most of the times,
01:11:53.660 --> 01:11:54.220
which is amazing.
01:11:54.480 --> 01:11:56.580
There's no training data about Tonio.
01:11:56.940 --> 01:12:01.360
And regardless, it was able to drive through stuff.
01:12:02.440 --> 01:12:04.880
So I guess it's the perfect moment to try stuff.
01:12:06.820 --> 01:12:11.440
I'd say if for any reason you're not happy with Asyncio,
01:12:11.700 --> 01:12:18.420
if for any reason you experience any of the pain points we talked about,
01:12:18.740 --> 01:12:23.460
I think it's like the perfect moment to try new stuff.
01:12:24.040 --> 01:12:28.440
And if you try Tonio and you have like a use case
01:12:29.000 --> 01:12:33.040
where it's not working or the API can be improved,
01:12:33.280 --> 01:12:35.540
like open an issue, open a discussion,
01:12:36.260 --> 01:12:39.120
ping me, DM me on Twitter.
01:12:39.590 --> 01:12:41.360
Oh, sorry, X the everything up.
01:12:41.540 --> 01:12:42.420
I will call it.
01:12:44.440 --> 01:12:47.120
Ping me and we'll figure out.
01:12:48.660 --> 01:12:51.100
Yeah, I think that's my message.
01:12:51.860 --> 01:12:52.580
Yeah, awesome.
01:12:52.640 --> 01:12:54.520
Well, it looks like a very ambitious project
01:12:54.880 --> 01:12:56.900
and I think you've done a lot of interesting things.
01:12:57.080 --> 01:12:58.520
So thanks for coming on and sharing it.
01:12:58.740 --> 01:13:00.800
Tell people where they can stay in touch with you as well.
01:13:01.140 --> 01:13:08.700
Yes, so my GitHub handle is G-I-0-B-A-R-O.
01:13:09.500 --> 01:13:14.120
You can find me with the same handle
01:13:14.140 --> 01:13:23.960
Twitter. I have a blog, even if I don't write a ton, but you can find my blog at blog.baro.dev.
01:13:24.380 --> 01:13:35.900
And if you want to join me and Marcelo Trilesinski, again, author of Uvicorn, Starlet, and a bunch of
01:13:36.800 --> 01:13:42.700
stuff he's making with TokenMaxing, we recently started a podcast available on YouTube.
01:13:44.440 --> 01:13:46.620
at the www pod.
01:13:47.060 --> 01:13:47.160
Nice.
01:13:47.600 --> 01:13:49.000
So people, check them out.
01:13:49.140 --> 01:13:49.680
Check out the pod.
01:13:49.840 --> 01:13:50.260
That sounds fun.
01:13:50.340 --> 01:13:51.620
I'll give it a look as well.
01:13:51.780 --> 01:13:52.920
Joe, thanks for coming on the show.
01:13:53.160 --> 01:13:53.880
Nice to catch up with you.
01:13:54.120 --> 01:13:54.840
Thank you for having me.
01:13:55.140 --> 01:13:56.400
It was super good.
01:13:57.700 --> 01:13:59.840
This has been another episode of Talk Python To Me.
01:14:00.220 --> 01:14:00.940
Thank you to our sponsors.
01:14:01.240 --> 01:14:02.420
Be sure to check out what they're offering.
01:14:02.660 --> 01:14:04.000
It really helps support the show.
01:14:04.560 --> 01:14:07.060
This episode is sponsored by Sentry's Seer.
01:14:07.380 --> 01:14:09.800
If you're tired of debugging in the dark, give Seer a try.
01:14:10.360 --> 01:14:12.500
There are plenty of AI tools that help you write code,
01:14:12.720 --> 01:14:15.580
but Sentry's seer is built to help you fix it when it breaks.
01:14:16.340 --> 01:14:20.420
Visit talkpython.fm/sentry and use the code talkpython26,
01:14:20.880 --> 01:14:24.040
all one word, no spaces, for $100 in Sentry credits.
01:14:24.740 --> 01:14:26.360
And it's brought to you by us.
01:14:26.960 --> 01:14:30.080
Talk Python and Python Bytes both now have MCP servers.
01:14:30.660 --> 01:14:33.460
Point your AI at 10 plus years of Python episodes,
01:14:34.100 --> 01:14:35.600
transcripts, and show notes free.
01:14:36.020 --> 01:14:39.800
Click MCP in the nav at talkpython.fm and at Python Bytes.
01:14:40.200 --> 01:14:42.080
If you or your team needs to learn Python,
01:14:42.380 --> 01:14:48.160
We have over 270 hours of beginner and advanced courses on topics ranging from complete beginners
01:14:48.450 --> 01:14:52.300
to async code, Flask, Django, HTMX, and even LLMs.
01:14:52.660 --> 01:14:54.860
Best of all, there's no subscription in sight.
01:14:55.400 --> 01:14:57.180
Browse the catalog at talkpython.fm.
01:14:57.860 --> 01:15:01.980
And if you're not already subscribed to the show on your favorite podcast player, what
01:15:01.980 --> 01:15:02.540
are you waiting for?
01:15:03.220 --> 01:15:04.960
Just search for Python in your podcast player.
01:15:05.120 --> 01:15:05.940
We should be right at the top.
01:15:06.380 --> 01:15:09.140
If you enjoy that geeky rap song, you can download the full track.
01:15:09.350 --> 01:15:11.260
The link is actually in your podcast blur show notes.
01:15:12.040 --> 01:15:13.420
This is your host, Michael Kennedy.
01:15:13.820 --> 01:15:14.880
Thank you so much for listening.
01:15:15.050 --> 01:15:15.880
I really appreciate it.
01:15:16.300 --> 01:15:17.020
I'll see you next time.
01:15:27.480 --> 01:15:28.420
I'm me.
01:15:29.820 --> 01:15:30.920
Get me ready to roll.
01:15:32.560 --> 01:15:33.700
Upgrade the code.
01:15:34.560 --> 01:15:36.240
No fear of getting old.
01:15:37.740 --> 01:15:39.720
We tapped into that modern vibe
01:15:40.160 --> 01:15:41.140
over Kenny's storm.
01:16:11.040 --> 01:16:11.060
Продолжение следует...