So you've created a web app using Flask, Django, FastiAPI, or even Emmet.
It works great on your machine.
How do you get it out to the world?
Well, you'll need a production-ready web server, of course.
On this episode, we have Giovanni Barriari
to tell us about his relatively new server named Greenian.
It promises better performance and much better consistency
than many of the more well-known ones today.
This is Talk Python to Me, episode 463.
Are you ready for your host, here he is!
You're listening to Michael Kennedy on Talk Python to Me.
Live from Portland, Oregon, and this segment was made with Python.
Welcome to Talk Python to Me, a weekly podcast on Python.
This is your host, Michael Kennedy.
Follow me on Mastodon, where I'm @mkennedy,
and follow the podcast using @talkpython, both on fosstodon.org.
Keep up with the show and listen to over seven years of past episodes at talkpython.fm.
We've started streaming most of our episodes live on YouTube.
Subscribe to our YouTube channel over at talkpython.fm/youtube
to get notified about upcoming shows and be part of that episode.
This episode is sponsored by Neo4j.
It's time to stop asking relational databases to do more than they were made for
and simplify complex data models with graphs.
Check out the sample FastAPI project and see what Neo4j, a native graph database, can do for you.
Find out more at talkpython.fm/Neo4j.
And it's also brought to you by us over at Talk Python Training.
Did you know that we have over 250 hours of Python courses?
Yeah, that's right.
Check them out at talkpython.fm/courses.
In fact, I want to tell you about our latest course we just released last week,
Getting Started with NLP and Spacey.
This one is written by Vincent Warmerdom.
You may know him from many of his educational projects and channels,
but he also worked at Explosion AI, the makers of Spacey.
So it's safe to say he knows his stuff when it comes to NLP and Spacey.
If you have text you need to analyze, pull entities from, understand the sentiment, and so much more,
then Spacey is one of the best frameworks out there for this.
And now we have an awesome course you can use to get way better at NLP.
During the course, you need a fun project, right?
Well, Vincent uses the past nine years of Talk Python transcripts,
along with a few data science programming bits of magic,
to process them all with Spacey and ask awesome questions like,
which frameworks are we talking about over the years?
Sign up for the course at talkpython.fm/Spacey.
And if you hurry and get it in the month of May, 2024,
we're doing a special 10% off to celebrate the launch.
That's talkpython.fm/Spacey.
The link is in your podcast player show notes.
Enjoy the course.
Now, on to that interview.
Giovanni, welcome to Talk Python to Me.
Hello, Michael.
Thank you for having me on the show.
It's great to have you on the show.
Some people you learn about just from like their public speaking or their writing,
and other people you meet through their projects, right?
I got to know you through Granian, your Rust-based Python and other thing, web server,
that I thought was really awesome.
Started playing with it, and we started talking on GitHub around some ideas.
And then here you are, sort of explore more, learn more about some of your frameworks that
like you'd created from.
So I'm excited to talk about Emmett, Granian, and a bunch of other things that you built
that kind of all go together in a big mix there.
Yeah, I'm excited as well.
Yeah, it should be a lot of fun.
Before we get into all the details of all that stuff, just tell us a bit about yourself.
I'm Giovanni Bariglari.
I actually born in Italy, but today I'm living in Vienna, in Austria.
I'm actually a physicist.
So yeah, I graduated in physics at the university.
And let's say I started working as a software engineer, focused especially on web software,
pretty soon after the university.
So it's like 10 years, something.
I'm working as a software engineer, also like as a cypher diabetes engineer.
So let's just say I'm quite like on the backend side of the things usually.
And I also started, I actually started like contributing to open source software projects,
even before actually starting working as a software engineer.
And particularly I started like contributing to the Web2Py project.
It's a quite old project by Massimo Di Piero.
And yeah, today I'm working as a cyber liability engineer for Sentry.
I bet that pretty much of the people know about Sentry.
Awesome.
Yeah, I didn't even know that you worked for Sentry until just a few minutes ago.
That's pretty awesome.
Obviously, people know Sentry.
They're big supporters of the show and sponsor some of the episodes.
But yeah, how's it like to work at Sentry?
Must be fun.
Well, it's super nice.
A lot of talented people.
They're super nice.
It's a really nice environment to be within.
So yeah, I'm super happy.
Yeah.
Awesome.
What does a software reliability engineer do?
So let's say it might be a complicated question because like actually the original title comes from Google.
So let's say it's kind of related to infrastructure and monitoring in software.
So let's say to simplify that it's about be sure that everything runs smoothly with no incidents and stuff like that.
I see.
Make sure you can monitor bugs, slowdowns.
Yeah.
work on failover type of situations, that kind of stuff.
Exactly.
I imagine you probably use Sentry to monitor Sentry for reliability.
Is that right?
Yes.
We have this project called like Sentry for Sentry.
Okay.
Which is like a separated Sentry instance that monitors the actual SAS instance of Sentry.
That's pretty interesting because of course, if Sentry went down, you're using it to monitor it.
Yeah.
Everyone else uses Sentry to monitor their thing.
It's not about when their code goes down, it doesn't affect it.
But when your code goes down, it might actually affect your ability to know that it's down.
So a separate copy.
That's wild.
Okay.
I hadn't even thought of that.
Exactly.
Super cool.
All right.
Now, first of all, there's a little bit of love out in the audience for your whole larger project, Emmett.
So Tushar says, did you say Emmett?
Emmett is amazing, which is super cool.
Tools like that encourage him to work on his dev tooling, which is really great.
Before we get into the details of that, though, why create another web framework?
I don't mean this in a negative way.
It's just like there's already, there's Flask and Django, and then we have FastAPI and so on.
So why not just go, oh, I'm just going to use this one?
Like what inspired you to go, like, I think I'll make one of them.
So I think we should go back a bit in time because actually like this year will be like the 10th birthday of like Emmett.
So let's just say it's like a long time.
So it's not that new.
Okay.
Out there.
Yeah.
I see.
Yeah.
So originally it was released as, it had like a different name.
It was called Weppy and I changed the name in 2020, I think.
like when I moved from synchronous paradigm to the asynchronous one.
So let's say at the time I designed Weppy, so the original version in 2014, the main thing was about, so in that time, it was like the time of Ruby and Rails being super popular.
And I originally started working in web development using Ruby and Rails.
And when comparing, let's say, the amount of, let's say, batteries included in the box of Ruby and Rails to the Python ecosystem.
So let's say that the major competitor at that point in time was Django.
But let's say the feeling I got from Django at that time compared to Ruby and Rails was completely different.
In a sense that I found myself like spending much more time on building stuff compared to Ruby and Rails.
And this is also what bring me to the Web2Py project, Web2Py community, because it was, in a sense, pretty similar in some of the design decisions with RAR.
But at the same time, like once you start contributing to a web framework, you have time to like to dig into a lot of the internals and decisions.
And so Web2Py at that time, so I used Web2Py to build my first, the code behind my first startup, actually.
And it had quite a lot of scaling issues at that time.
So let's say at that point in time, I just was looking out for the options and I started like digging into the code internals of Django and also Flask, which I mean, I really loved like the Flask approach of things.
But at the same time, it was so micro.
Yeah.
I mean, like to build an actual project, it required like to have like tons of extensions and other pieces, let's say other libraries to add it to the project that, yeah, I think like I ended up just, you know, saying, okay, let's just rebuild Web2Py the way I wanted.
And that's eventually how Web2Py came out today.
Yeah, that's pretty much the story behind it.
Yeah.
Okay.
Yeah, I didn't realize it went that far back.
How about Granian?
Is that newer?
Yeah, Granian is, I think like the first public release is like from one year ago or something.
Yeah.
And I, because I learned about Emmet through Granian and like, oh, it's kind of all, probably all the same project.
I didn't realize the history.
Why the new name?
Why Emmet?
So the thing was that to support, let's say the upgrade between Web2Py and Emmet.
So since like all the interfaces has to be changed to support like async code, the idea was to provide, let's say, a quick way to do that.
Meaning that to make it possible for developers to, you know, install like a new version of Web2Py and getting like everything broken because of, you know, the new interfaces.
So yeah, I just decided to, you know, changing the interface and also changing like the package name in order to say, sure.
Okay.
If you want to upgrade, you can upgrade safely.
Otherwise, it's like a super mega version change.
Not only you change the version, but you change the name.
Yeah.
I see.
Exactly.
That's interesting.
All right.
Well, let's dive into it.
So I like the title, Emmet, the Web Framework for Inventors.
And yeah, maybe give us a sense of like, what are some of the core features of Emmet?
And what are your goals with building it?
From an API perspective.
The idea was to have like all in one, let's say, framework to build web application.
All in one, let's say, in a sense of, again, when the project actually started.
So like even 10 years after that, I still usually prefer to develop web projects without reliant too much on front-end frameworks.
So this is like a big, let's say, preamble to the thing.
Like this is originally from an era where like front-end web framework did exist.
Like I think it was just AngularJS and maybe Ember at that time.
Yeah.
I mean, you're basically describing my life in 2024.
So I'm a big fan of the server-side frameworks, you know?
Yeah.
Also because like it seems sometimes that we reinvent like a lot of stuff to catch up like the beginning at the end.
Like, yeah, I saw like all of the theme about, you know, server-side rendering with front-end frameworks and server-side render components and all that kind of stuff.
So sometimes it just feels, you know, we're getting back to the origin.
But yeah.
So the idea behind Emet is to have like all-in-one solution to develop web applications.
So you have all the standard features you have with the web framework.
So like routing and middlewares and that kind of stuff.
You have an ORM.
You have a templating system plus a few, let's say, tools embedded with Em.
So for instance, it's very easy to use, I don't know, sessions or to have an authentication system.
It's all like provided inside the box.
So yeah, the idea was to have like, let's say, a battery of tools like in one place to do the most common things when you start developing a web application.
Yeah, very nice.
So yeah, like you said, it has an ORM built in and it feels, I guess, SQLAlchemy-ish in a sense, but not exactly the same.
Or Django ORM would be, you know, another way in some ways there.
Yeah, I think it's more near to SQLAlchemy in that sense.
You tend to have like an API for using Python objects to build queries rather than, how to say, use like a lot of strings attributes like you usually tend to do in Django.
Yeah, I mean, it's more close to SQLAlchemy in that sense.
I think like the major difference with the ORMs out there is that the model class you define are not like, so when you, for example, select records from the database, the single, let's say, rows you select are not instances of the model class.
So let's say like the model class acts more like management class.
Like a schema definition sort of thing.
Yeah.
I mean, it does like a lot of helpers top of that.
But yeah, I think like it's definitely the major difference between like the vast majority of ORMs out there for Python.
When you usually have like the model class, which is also like the class of all the records you select and work on from the database.
Yeah.
So what do you get back in this world here?
What do you get if you do a query?
Like in your example on the homepage, you have a time traveler.
So what do you get back when you get a group of them, a set of them?
So you get like a different class.
So there's like a separated class.
Every model has, it's called like row class.
So it's an instance of that class.
And this design, it's mostly made for two reasons.
Like the first one is performance in a sense, meaning that when you select records or operate on records,
it avoids to, you know, fulfill like all those objects with the actual model class attributes or functions or methods.
And the validation and stuff.
Yeah.
And on the other end was also to kind of remind to the developer that he is working with actual data from the database
and not like real Python objects in a sense, which is.
Yeah.
I think like in the years is like the first reason why people tend to object against ORMs.
So.
Yeah.
Those two were the main reasons behind this design.
It's something like, you know, in the between of an ORM and just some database abstraction layer.
This portion of Talk Python to Me is brought to you by Neo4j.
Do you know Neo4j?
Neo4j is a native graph database.
And if the slowest part of your data access patterns involves computing relationships,
why not use a database that stores those relationships directly in the database,
unlike your typical relational one?
A graph database lets you model the data the way it looks in the real world,
instead of forcing it into rows and columns.
It's time to stop asking a relational database to do more than they were made for
and simplify complex data models with graphs.
If you haven't used a graph database before, you might be wondering about common use cases.
You know, what's it for?
Here are just a few.
Detecting fraud.
Enhancing AI.
Managing supply chains.
Managing a 360 degree view of your data and anywhere else you have highly connected data.
To use Neo4j from Python, it's a simple pip install Neo4j.
And to help you get started, their docs include a sample web app demonstrating how to use it both from Flask and FastAPI.
Find it in their docs or search GitHub for Neo4j movies application quick start.
Developers are solving some of the world's biggest problems with graphs.
Now it's your turn.
Visit talkpython.fm/neo4j to get started.
That's talkpython.fm/neo, the number four, and the letter j.
Thank you to Neo4j for supporting Talk Python to me.
I like the query syntax.
You know, people visit the homepage, you'd see something like time travel dot where, then lambda of t goes to t dot return equal equal true.
And while some of the ORMs let you write code in terms of like the class fields or whatever, it's never looked quite right because you're working with, say, the static value out of the class.
Whereas what you really are trying to talk about is the instance level of the record, right?
So instead of saying t, you'd say time travel dot return, but we'd never test that because it's the global value of it, right?
And stuff like that.
Or you just use strings, which is basically, in my mind, no good.
But what's cool, you know, also, do you want to do an OR or an AND?
And then what weird thing do you import to do the OR?
And like, you know, how do you wrap the query in it?
All that kind of stuff, whereas if it's a lambda, you can just express the conditions how you want.
Yeah, yeah.
That's pretty much the idea.
So I like to use, you know, special methods from Python objects and translate those expressions like in actually SQL code.
So, yeah.
Nice.
For my apps, I have a combination of Beanie and Mongo Engine, depending on which one you're talking about.
And for Mongo Engine, you do things that are pretty funky.
Like, if you want to say greater than, you would say time travel dot, I don't know, it doesn't have a value, but age.
Let's say there's an age.
Like, time travel dot age underscore underscore GT equals value.
And you're like, yes.
Well, it's not equal to it, and that's not the name of it.
But okay, I guess that means, you know what I mean?
Like, there's a real weird way it's, like, jammed into a syntax, whereas, like, here you say greater than whatever, right?
Yeah.
Yeah, it's, like, the same of, it's one of the things I dislike still today of Django or M.
Yeah.
In that sense.
I mean, it has, like, a lot more capabilities.
Because, for instance, like, when you want to represent, like, complex queries, it tends to be more powerful in that sense.
Meaning that special methods are limited.
So, at some point, you start making custom methods.
So, like, I don't know, starts with, for example.
Yeah, starts with, or in this set, or the set includes this, and something like that, right?
Exactly.
So, I think, yeah, there are pros and cons in both, let's say, approaches.
Yeah, cool.
All right, so, we have a lot to talk about, even though all this code fits on one screen.
The other part is to define an endpoint.
This is about an API, right?
So, you have an async def, which is awesome.
Supports async and await.
I think it's super valuable.
Yeah, one note is that the RM is still synchronous.
Yeah, yeah.
So, what about that?
Are you planning on adding an async thing, or are you just saying it's just synchronous?
So, it's like a very long story, in a sense, because, like, I started asking myself the same question, like, several years ago.
And I think, like, at some point, probably, I will end up doing that, in the same way SQLAlchemy did that.
Even if I remember, like, a super nice blog post from the author of SQLAlchemy, stating that asynchronous code and databases are not the best way to use that.
So, yeah, let's say, like, in the last few years, I just waited, in a way, to see what everyone else was doing.
But, yeah, I think, like, at some point, it will be inevitable, in a sense.
I just don't feel the time has come yet.
So, we'll see.
Yeah, cool.
And then, I guess, the last thing to talk about is you have a decorator app.route, pretty straightforward.
Yeah.
But then, you also have an at service.json.
What's this decorator do?
So, you can think about that decorator, like, the service decorator, as, like, the JSONify function in Flask.
So, yeah, in Emmet, you have, like, both the JSON service and the XML service.
Because, like, in old times, I had to write stuff to talk with XML and points and stuff like that.
Yeah, yeah, yeah.
So, yeah, it's just an easy way to wrap and say everything that returns from this function, just serializing JSON or XML or whatever.
If I return, rather than a response, just return a dictionary and it'll do the serialization, right?
Yeah, exactly.
Nice.
And the audience, let's ask, does it generate an open API documentation?
Like, does it automatically generate documentation?
So, from standard routes, no.
There's an extension, though, meaning that if you plan to design REST, let's say, APIs with Emmet, there's an extension for that.
It's called Emmet REST, which, let's say, gives you, like, more tools to structure your routes and serialization and deserialization and all that kind of stuff.
And that extension also brings OpenAPI documentation generation.
Eventually, let's say, the OpenAPI documentation generation will come also to plain routes in Emmet.
But there's quite a few design implied to do that.
Meaning that, so Emmet, it's, like, not designed to have a strong type system.
Because, again, it comes from the days where, like, typing.
That didn't exist?
Let's not, yeah.
So, let's say that, for instance, for frameworks like FastAPI, which are practically designed on top of something like Pydantic.
So, you have, like, a strong type system.
So, everything that comes in and out from the majority of routes you write has types.
And so, it's really easy for the framework to inspect the code and understand what's going on.
On, let's say, general frameworks like Emmet, where you, I mean, you might have, like, I don't know, HTML routes or other kind of stuff going on.
There's no, let's say, design behind that to support in the first play, like, strong typing.
So, yeah.
Making, like, OpenAPI documentation out of standard Emmet routes involves, like, quite a lot of decisions.
So, yeah.
We'll see.
Yeah.
Okay.
Yeah.
Very cool.
Yeah.
We'll come back and talk about Emmet Rests in a minute.
That's one of the fun things.
That also has WebSocket support, right?
Yep.
Okay.
Absolutely.
WebSockets are these things that I'm always like, man, they're so cool and you can do all this interesting stuff.
And then I never, ever, ever have a use case for it in my world.
I just haven't yet.
And so, I'm like, well, they're very cool, but I don't have it yet.
Yeah.
So, I mean, I'm not building Slack.
Yeah.
The thing is that usually, like, when you work with WebSockets, it's also pretty common that you need some broadcast facility.
Yeah.
So, usually you want to do channels or that kind of stuff, which usually tends to involve, like, other software.
Like, you usually have Redis or something like that in order to, since Python is not exactly good in, let's say, managing threads or communicating across different processes.
That's probably why it's not so easy in the Python world to actually rely on WebSockets a lot.
I don't know, for instance, if you take, like, languages like, I don't know, Alex here, or you have, like, tons of stuff based on the fact that everything is actually communicating over Socket.
So, yeah.
And I think, like, one single thing to say on WebSockets, it's, I think Emmet is the only or one of the few frameworks that allows you to write middlewares with Sockets.
So, you can.
Okay.
So, if you have, like, your chain of middlewares on the application, you can also define behaviors for the same middlewares to behave on WebSockets.
So, you can probably use, like, a lot of code.
Like, I don't know, if you are in a WebSocket and need to talk with the database, you can use the same middleware for the database connection you use on the standard request.
So, I think that might be worth noting.
Yeah, absolutely.
Another thing that's interesting that I don't see in a lot of ORMs, they kind of just leave it to the, well, in SQL, so you write it, is aggregation.
Right?
The aggregation stuff you have here is pretty interesting where you do a bunch of calculation type stuff in the database and then get a, sort of, the results back.
Right?
So, here you can say, like, select, talking about an event, like, event.location.
Get the counts.
And then group by this thing, order by that.
Having these sort of properties.
That's pretty unique.
I don't see that in a lot of ORMs.
Yeah, I think, like, you can do pretty much the same with SQLAlchemy, but probably, like, the syntax is less sugarly, let's say.
I mean, again, this design comes from the fact that with my first startup, we had to do, like, a lot of aggregation over the database.
So, that's why I wrote all of that, you know?
Yeah, that's cool.
The same code.
Yeah, nice.
You know, I'm familiar with it from all the MongoDB stuff that I've done, like, that big aggregation pipeline over there as well.
Yeah, I'm also familiar.
Like, I'm not a huge fan of Mongo, though.
Probably because, like, being an SRE, like, making Mongo reliable is, like, a mess sometimes.
I think it depends on how people rely on it, right?
Yeah.
For me, it's been absolutely.
I've run my stuff on over 10 years, and it's been perfect.
However, that's because I use a lot of structured code to talk to Mongo from one tech stack, right?
But if some people are using dictionaries to talk to it, other people are using this framework, other people are using that framework,
then the lack of schema structure, I think, becomes a problem.
So, I think it really depends on how you use it.
But, yeah, I hear what you're saying for sure.
I think that that's not even necessarily a Mongo challenge.
That's a document database challenge, generally, right?
Yeah.
Just Mongo is primarily the way people do document databases.
Yeah.
I tended to, like, use it for separated stuff.
So, in several projects I worked on, I had, like, for instance, like the main database with Postgres, for instance,
like another database with Mongo for specific stuff.
Maybe stuff you don't need transactions on, or maybe you want to store, like, time series data or, you know, that kind of stuff.
So, for that, I think it's really cool.
Yeah, nice.
All right.
I guess one final thing here that is worth covering, then I want to dive into a grand new as well, is the template syntax.
So, you've got your own template syntax that's kind of like...
That's not a syntax.
All right.
You tell people about it.
You tell them about it.
Yeah.
So, the template system embedded in Emmet is called Renoir.
And the idea behind it is to don't have a syntax at all.
So, the idea behind Emmet template system was why...
So, the question I had is, like, why do I have to learn a new language to write server-side render templates?
Like, why it came out?
Yeah.
And those languages are...
Yeah.
And they're very, very Python-like, but they're not Python-like.
Exactly.
So, I just said, well, I guess I'll try to do just Python, you know, wrap it in the same brackets every other templating language has.
So, it's just plain Python.
You can do pretty much everything you can do in Python.
You can even do imports inside.
Not that I suggest to do that, but you can...
Still, you can do that.
Don't go create PHP, people.
Come on now.
Exactly.
The only, let's say, major difference from standard Python code is that you have to write the pass keyword after a block of code.
So, if you write, like, a for loop or an if statement, the template engine has to know when that block ends, given that Python relies on indentation to understand like that.
But in templates, you don't have like the same indentation level you have in Python.
So, that's the only major difference from plain Python code plus a few, let's say, keyword added to the game.
So, you have extend and include in order to extend and include.
So, there are partial templates, let's say, and blocks.
That's it.
Right.
Blocks for the layout sections, right?
Exactly.
Yeah, that's really nice.
I like the idea.
I'm sure there are people listening that'd be like, I would like to try this stuff out, but I've got 10,000 lines.
Of Jinja.
Jinja, or I've got 10,000 lines of Chameleon or.
Yeah, yeah, yeah, I know.
Whatever.
What's the story?
I mean, I'm working in the office with Armin Ronaker every day.
So, the amount of Jinja code we have in Sentry is like huge.
So, yeah, I perfectly understand the point.
I don't have like, let's say, a marketing line for selling a Renoir.
It's just something that I, so today I'm so, I'm equilibrary familiar to Jinja templates and Renoir templates.
I'd say it really depends on how you usually structure your application code.
So, I think one good way to try out Renoir is if you tend to don't use a lot like Jinja filters or stuff like that.
That might be a good case scenario to trying out Renoir.
Yeah.
But, of course, it has to be like a new project because converting, I mean, there's no sense into moving, translating code from one system to another once you picked.
It's not super different.
So, I think you change an end if to a pass, for example, or end for into a pass.
But, I was thinking more, is there a way to use Jinja within Emmet instead of using Roar?
I mean, there's no plain, there's no read extension for that.
But, I mean, if you create like a Jinja instance over the Emmet application, you can call it in your roots.
You can even create a middleware for that.
So, I think it's pretty easy also to set up Emmet to work with Jinja.
Yeah, I would think so.
I created a chameleon FastAPI, which lets you basically put a decorator on FastAPI endpoints.
And it does chameleon template rendering with a dictionary instead of rest endpoints.
It wasn't that much.
You basically just have to juggle it from behind.
So, I imagine you could probably, someone could create a Jinja decorator.
Like you have service.json, like a template.jinja or whatever, something like that, right?
Probably?
Yeah, yeah, absolutely.
That said, I'm not a fan of Jinja.
I think it's overly complicated.
So, I'm not encouraged.
I'm not suggesting it.
But the reality is, even as much as I've tried to fight against it, is that the majority of Python web HTML, dynamic HTML, is done in Jinja these days, right?
Yeah, probably too.
Yeah, you kind of got to live in that space, even if you don't want to.
All right.
And let's talk about the thing that I opened, I talked about at the opening, is Granian.
Where's Granian gone?
There we go.
So, this is how, as I said, I got to learn about this framework and what you're doing and stuff with Granian.
Tell us about Granian.
And as a way to sort of kick this off, Cody, who I've had on the show before from Litestar, says,
thanks for the work on Granian.
I've had an excellent time using it with Litestar.
Litestar is also awesome.
Yeah, thanks, Cody.
Yeah, so tell us about it.
Yeah, so as the description suggests, it's just an HTTP server for Python application.
So, it has the same scope of UVcorn, G-Unicorn, Hypercorn, and all that libraries.
The main difference compared to every other HTTP server for Python application is that it's not written in Python.
It's written in Rust.
And it supports natively both BoostG and ASGI, so both synchronous and asynchronous applications.
Plus, a new protocol I also wrote with Granian, which is called RSGI.
But the only existing framework using it that I'm aware of is Emmet, indeed.
Yeah, I think there's a lot of things that are nice about this.
And I have actually most of the things, including Talk Python itself, running on Granian, which is pretty cool.
Cool.
Yeah, yeah, absolutely.
So, single correct HTTP implementation.
Sounds awesome.
Support for version 1, 2, and 3, I guess, when it's ratified, right?
Yeah, so HTTP3, let's say, so since Granian is actually based on a Rust library, which is called Hyper, which is a super cool library.
It's like vastly adopted, like everywhere in the world.
Like, I don't know how many thousands, hundreds of thousands of libraries in the Rust ecosystem use it.
It's used in Cloudflare for a lot of their production systems.
So, super strong library.
But yes, it doesn't yet support HTTP3.
So, yeah, I guess when Hyper will support HTTP3, that could be easily added to Granian.
Right, right.
That's cool.
Yeah, with things like Genocorn, you've then got to also integrate Uveacorn workers, and you kind of have a lot of stuff at play, right?
So, here, you've just got one thing, which is cool.
Yeah, I mean, like, I tended to find annoying the fact that if you want, like, to squeeze out, like, performance out of Uveacorn, you usually need to, yeah, pile up different libraries together.
Like, oh, wait, I need to add the HTTP tools dependency, so it can use, like, the C written parsers for HTTP.
I'll wait, and probably I want some process management, so I need also a Junicorn.
Yeah.
It's not super easy, like, for starters, at least.
Yeah.
I guess maybe we should just set the stage a little bit for people that don't live and breathe Python web deployment.
Apologies.
So, typically, you would have something like Nginx or Caddy that browser actually talks to, and then behind the scenes, you set up those, let's just say, Nginx, to when there's a request for a dynamic content or Python-based content, as opposed to, like, a CSS file or something.
So, again, you can use it as a CSS file or something.
So, again, you can use it as a CSS file.
So, I mean, having Nginx above Granian makes sense only if you want to root something out of Granian and not serve it from Granian.
But, yeah, in general, I'd say that you can use it in both ways, behind Nginx or not, up to the specific needs of the application, let's say.
Yeah.
I have one Nginx Docker server container handling, like, 15 different apps.
And so, for me, that's kind of the setup.
But typically, the SSL that I do is over Let's Encrypt using Certbot.
If I want to do HTTPS with Granian, how do I do it?
You can keep, like, the Let's Encrypt and Backman HSH generation thing because Granian supports the SIGAP signal.
So, whenever you need to refresh the certificate, you can issue a SIGAP to the Granian process.
And that process will reload the workers picking up the new certificate.
So, I think it's pretty straightforward.
I mean, if you already manage, like, SSL certificates and, like, renewal chain and all that kind of stuff, it's pretty straightforward to do the same in Granian.
You can just pass, you know, the paths to the certificates to the CLI command or even use environment variables up to you.
Got you. Okay.
One thing that I thought was pretty interesting was the performance.
Not necessarily that it's so, so, so much faster, but that it's so, so much more consistent.
You want to talk about that a little bit?
Yeah.
So, I think if you want to show to the YouTube audience the comparison thing, you'll find the link in the bottom of the page.
Because the first phase of benchmarks in the repository contains just benchmarks of Granian itself.
Whereas in the versus page, you can find, like, comparison with other servers.
So, the thing behind, let's say, the stable keyword I used into describing, like, the performance of Granian was about the fact that usually when people look at benchmarks, just look at the, you know, number of requests.
Yeah, yeah, yeah, yeah.
What's the max request per second you can get with this thing or whatever?
Yeah, exactly.
But the, another, like, very important value of this to me is like the latency, because you can, you can still serve like a lot of requests in parallel, but the amount of time each request will take to be served.
It's also like important.
I mean, I can serve like a thousand records per second, but if those requests takes a second or it takes like 10 milliseconds, it's like a huge difference for the end user.
Yeah.
And so, the thing is that from, from, at least from benchmarks, it appears that the way Granian works, which relies on having like all the network stack separated from Python.
So all the IO, the real IO part involving the network communication is not tied to the Python interpreter.
And so it doesn't suffer from the global interpreter block and threats getting blocked between each other.
It seems to make like, like, like, Runyon to be more, let's say, predictable in response time, meaning that the both the average latency and the maximum latency you have in the benchmarks is much lower compared to other, let's say, implementations, other HTTP servers.
So yeah, it's not like super faster.
It won't make like, obviously it won't make the Python code of your application faster.
We can shut down all of our servers except for one $5 digital ocean server and just.
Yeah, no, not really.
Yeah.
No, not really.
But at least it should normalize in a way the response time of your application.
Yeah.
So the standard deviation of the request time is way, way tighter.
Exactly.
The distribution of the request time is way, way tighter, even though you do seem to have generally the fastest times.
But if you look at the difference of the average times in the max times.
Yeah.
The difference is a lot smaller.
It's like two and a half times variation versus some of the other ones are many.
Yeah.
10 X or something.
Yes.
Yeah.
Maybe a hundred X or some of them.
Yeah.
Yeah, absolutely.
Okay.
Yeah.
That's what really I thought was pretty interesting is the super predictability of it.
Yeah.
One thing I want to ask you about is you did say it does this RSGI.
Do you want to call it G?
G SGI, the Granian server interface?
No, whatever.
No, it's like a RAS server gateway interface.
Yeah.
That's what I figured.
And you said Emmet uses this, which is awesome.
What's the advantage?
Is there a significant advantage to doing things differently rather than ASGI or something?
Would it be worth like things like flask saying, Hey, should we support this if we're running on top of granny?
Or things like that's what I'm getting at.
So I didn't actually know if flask today also supports a synchronous.
With court they do.
Request.
Yeah.
Okay.
So Quartz might take advantage of RSGI, meaning that it's still asynchronous protocols.
So you have to be in an asynchronous context to use RSGI.
But the main difference, let's say between ASGI and RSGI is that it's in the, how to say, the communication mechanism or let's say the communication entities, meaning that.
So in ASGI, you have usually have two methods, two awaitable methods, which are like send and receive.
So we have to push, let's say dictionaries to those methods, which are referred as messages.
So usually have like a dictionary, which has type key, which contains like the type of message, which might be, I don't know, HTTP request or HTTP body or WebSocket message.
And all the intercommunication between like the server and application relies on those dictionaries with specific keys and strings.
And since you have like a single, let's say, interface to rely on that, and that interface is asynchronous, it also means you, it means two things.
The first thing is that every time you want to say something to the server or to the client, you have to await for that message, even if there's no actually IO involved in that operation.
So, right. Which is a context switch and overhead and all of that stuff, right?
Exactly. So for example, when you, so sending back a response in ASGI, it involves typically at least two messages.
So the first one is to start the response. So you instruct the server with the response code and the headers you want to send back to the client.
So the following message or messages are the body of that response.
So the final fact is that the response start event doesn't involve any IO at all. It doesn't use the network.
So what happens in that is that you delaying the operation that you are supposed to do, which is just saying, okay, I'm gonna send some data.
And these are the like, here's some text. Yeah. You're gonna delay that operation to the next cycle of the event in your Python code.
So that adds quite a lot of overhead. And I mean, I understand like why the interface is made in this way, because it's like super straightforward.
It's very simple. You have like the same interface to do everything, but at the same time, it feels very unperformant in a way because we are wasting like a tons of like, even, I don't understand why do we need like to waste event loop cycles to do something that is actually synchronous code.
Yeah, sure. And so RSGI changed this in a way that you have interfaces which are synchronous or asynchronous depending on what you're actually planning to do.
For example, like if you have the entire body. So if your root returns, I don't know, a JSON stream, okay, you don't need to actually await for sending the body because you already have like all the bodies. So the interface in our
Right, right. It's all in memory. Yeah, there's no IO.
Yeah, the interfacing.
It's not like a file, file stream pointer or whatever they said to return. Yeah, exactly. So in that case in RSGI, you can use a synchronous method to just move the body to the server.
to the server and just let the response goes nice. Whereas if you want to stream content, then you can use a specific interface for that in RSGI, which is responsible stream.
And that gives you like an interface to send chunks of body or iterate over something as you're supposed to do.
Oh, yeah. So that's the major thing. The other thing, like the other reason why RSGI exists is that.
Yeah, ASGI is designed based on the fact that the network communication happens under Python, which is something that Granian can do because can emulate because it supports ASGI.
So that's also waste. So if you have the chance to have a different implementation that makes like things a lot more difficult to implement, meaning reasoning, like if you work in Python, but you're actually in a different language.
So yeah, that's the other reason why RSGI exists.
Okay. Yeah, that's very interesting.
Yeah, maybe some of the other frameworks could look at that and go, well, if it's available, it's an option.
Okay, a couple of things I want to talk about before we run out of time here.
One is Jazzy Coder out in the audience asks, how did you validate your library following the correct spec?
Did you reference the RFCs or another library or if you use a go-down, back to principles of the Unix networking programming book?
And for background, interested in this approach because I'm building my own WSGI server.
Okay, cool. So the idea, I mean, WSGI protocol is like documented in a PEP.
So I just implemented tests that respect what is defining to the original PEP about WSGI with just an exception.
So the only exception in Fragornian in WSGI protocol is that it's able to serve HTTP/2 over WSGI,
which is not supposed to happen. But with Granon, you can serve your WSGI application directly with HTTP/2.
But yeah, that's the way I was sure to respect the protocol.
Yeah. How about like the HTTP/2 protocol? Are you using just a library that already has it all figured out or?
Yes, yes. I mean, reinventing the wheel like also for HTTP handling was something I wasn't looking for.
No, I wouldn't want to do it either.
So yeah, hyper hyper is, I mean, I don't know.
It's a REST crate or something like that?
Yeah, exactly.
Awesome. All right. Very cool.
The other thing I want to ask you about or just let you speak to real quick is there's a bunch of features like specifying the HTTP interface level.
Like, do you want to restrict it to one or two? Yeah, you might care because there was a vulnerability in HTTP/2 create like some kind of too much work or too many retries or something recently.
So maybe you want to switch it to one for a while. I don't know.
Fun fact that Granian wasn't affected by that because hyper the library behind it wasn't affected by that.
Oh, nice. That's awesome.
Yeah, I figured basically just in this case, you wait until hyper either fixes it or hyper is not a problem, right?
Which is great. But maybe just talk about some of the things that we haven't touched on that are interesting like blocking threads or threading mode or specifying the loop or so on.
So yeah, in Granian, so since Granian has this unique architecture where you have an event loop running on the RAST side.
So for instance, if you're like deploying your ASGI application with Granian, you will have two event loops like the Python one, the ones that runs your code and also a RAST event loop,
which is actually the Tokyo runtime is another super popular crate in the RAST ecosystem.
There are different ways to run the RAST runtime, meaning that RAST is not limited to having a single thread running the loop.
And thus you can have an event loop running on several different threads on the RAST side.
And so the threading mode option in Granian lets you specify that behavior, meaning that if you use the runtime option, you will end up having like multi-threaded runtimes on the RAST side.
Whereas if you specify the workers option for the threading mode, it will still use a single threaded runtime also on the RAST side.
If you say that the runtime mode, did the workers themselves each get multiple threads?
Is that how that works?
Yes, exactly.
So in runtime mode, every worker has multi-threaded runtimes, whereas on the worker side, you have like the worker is also the runtime.
Yeah, got it.
And the option is there because like depending on the load of your application, like one of the two might work better.
Sure.
Depends on the IO and CPU boundness of your application.
So, yeah.
I don't want to go too much into these.
But if I set threading mode to runtime, is it reasonable to have just one worker?
Or does it still make sense to have multiple workers for Python app?
So the thing is that with a single worker, so the workers will spawn their own Python interpreters.
So every worker is limited to the global interpreter lock, meaning that even if you spawn like a single worker with, I don't know, 12 threads, those 12 threads will run in the RAST code.
Yeah, yeah.
But they share a single Python runtime, which means all the things that that means.
Exactly.
Got it.
Okay.
So the only way to scale.
So the workers is the way to scale, let's say the Python code of your application.
Okay.
Threads and threads are useful to scale the RAST runtime of stuff.
No, there are some, the RAST side of stuff, of the things, meaning that those will be like the amount of threads used by RAST to handle your requests.
So for example, if your application runs, opens like a tons of WebSocket, maybe you have like a WebSocket service.
It might be helpful to spawn more threads for the RAST side.
So it can actually handle more of those requests in the WebSocket land.
And the blocking threads are mostly relevant only for the VOOSKI protocol, meaning that the blocking threads are the amount of threads used by Granian to interact with Python code.
So on ASGI, since you will have like the event loop, there's no so much difference in how many threads you, how many blocking threads you spawn because those blocking threads will still have to schedule stuff on the Python event loop.
But on VOOSKI, since you don't have, you're not limited to the main thread of Python.
So if you're, I don't know, maybe your application is using, you're using CyperPG to connect to the database and those libraries are able to release the global interpreter block.
So having multiple blocking threads on VOOSKI might be helpful, still be helpful because like all the code which doesn't involve the GIL will be able to run in parallel.
Right, right.
Maybe one part, one thread, one request is waiting on a database call, which it hits the network, which releases the GIL, for example.
Right.
Exactly.
Yeah.
Okay.
What about this loop optimizations, this opt/noop?
Yeah, that's, that's a good thing.
Yeah, that's, that's...
What kind of magic is in there?
Uh, that's a bit complicated, meaning that, so I think like writing Runyon was like super helpful for me, at least to understand like the internals of Async KO in the Python world.
And if I have to be honest, I don't really like how Async KO is implemented behind Hoot.
But anyway.
Yeah, I feel like you have to juggle, you have to be so aware of what loop is running.
Has a loop been created?
Is there a different one?
Have I got the wrong loop?
Like all of that stuff, it should be utterly transparent.
And I just, I should just tell Python, I want to run stuff in a loop.
Yeah.
You know, I don't want to, it's not like I'm managing the memory or juggling, you know, the GC.
Like I feel like Async IO should be the same.
You should say, I want to run stuff asynchronously.
And maybe somewhere I've configured that or maybe it's automatic or whatever, but just do any, you're always kind of like, you know, for example, if you talk to the database asynchronously at the start of your web app, and then you use FastAPI and you try to use it in your request there, it'll say, well, you're on the wrong event loop.
Like, well, why do I care about this?
Just run it on the loop.
Like, you know what I mean?
Like that's kind of, that's been a complaint of mine since 3.5, but hey.
Yeah.
Nobody asked me, so.
Yeah.
So yeah.
And those, let's say optimizations are actually a few hockey parts.
In fact, like, I think like FastAPI doesn't work with loop optimization enabled with Granian because it skips.
So those optimization just keeps like one of the first iterations into running asynchronous code.
I think going more deeper than this in details would be.
Yeah.
Okay.
Not, not worth it, right?
Long and hard to follow.
But let's just say it just keeps some steps in the task running in the other one.
Yeah.
Okay.
You can do things like specify the SSL certificates and stuff.
This one right here is pretty excellent.
I mean, I don't know who worked on that one, but I didn't work out, but I inspired, I requested this one as the right way to put that.
You can say the process name, which is nice.
Yeah.
If you're running multiple sites, all on Granian on the same server.
You can differentiate which one is using the memory again.
And while we're looking at this, can I ask, can I propose an idea and just get your feedback on it?
Get your thoughts.
What about a lifetime management type of feature where you say after 10,000 requests, just recreate the worker or after an hour recreate the worker or something like that?
Is this something that is in any interest to you?
I understand the need for that, especially for, I think it's like one of those requests that is based on the fact of using like Boosgi or at least like it's coming more from the jungle user land.
So I think making a lifetime that kind of check would be kind of hard in a sense that there's a lot like involved into the, you know, process management of Granian because like, you know, the Python process and then you have Python threads and then you have like the rust threads and then you have like the runtimes.
Yeah.
And then reasoning of lifetime probably it's kind of hard.
I think like the fixing, like a maximum number of requests per worker is something that can be done, let's say pretty easily.
That issue is like, it's opened by, there's an issue for that, like opened by a few times, if I recall correctly.
The thing is that like in the, let's say in the prioritization queue.
It's not at the top.
So let's say at the moment, like I'm talking with some people that propose their self to join as a contributor on Granian, but let's say at the moment I'm still like a single main contributor.
So yeah, sure.
I need to make, you know, some priority queues into issues.
A look after your wellbeing.
Yeah.
I think like the one which is more requested right now is like the access log.
Yeah.
So I think like that would be the next one probably.
Yeah.
I think honestly, that's more important of the access log than this one.
Yeah.
There's something changed in one of my apps and it just all of a sudden slowly just keeps growing in memory.
And I'm pretty sure I've not done anything to make those changes.
And it's something in a third party library, data access database or something else.
I don't know what it is, but it's just, and it's fine, but it just consumes so much memory.
And so I ended up setting up some other thing to just, just say, look after like a day, just, you know, just give it a refresh.
Let it refresh itself.
You know that we sent the SDK now we have also profiling.
So you can actually look into the stocks of memory locations, even like live on your application.
So that's true.
If you need to that like something like that, you can try with century.
Like distributed tracing or something like that.
Yeah.
Or the APM stuff.
I mean, this should be dressing is more like about chaining together, like different sources.
Like this is profiling, like.
Yeah.
Okay.
Yeah.
Flame graphs and stuff to see where does your application.
Maybe I'll put it in.
Cause it's, it's driving me crazy.
And I would love to just do a PR to somewhere and just go, Hey guys, this change, here's the problem.
Or if it is my problem.
And I just like, there's nothing I really changed in the core of this thing, but it seems to have started going weird that maybe I don't know.
But anyway, it would be great.
I'll have a look at it.
Thanks.
All right.
What's next for Grandin?
Yeah.
I think like fulfilling a couple of feature requests, like the access log, like the worker max requests or a few, let's say minor things in that sense.
I think like in terms of major features, it's pretty solid at the moment, as a server after let's say the idea is like after these feature requests, the idea was to, I mean, it's just an idea at the moment, but I'd like to try to add some features to the RSGI protocol.
So for example, we talked before about channels and web sockets.
So as I said before, like I find very annoying, like every time I want to make even just a chat room, I need to, you know, put red is there and manage red is and whatever.
And not like that kind of complexity to my project.
And so I was thinking about like embedding some broadcasting features into the RSGI protocol, because, you know, while all other servers for Python are written in Python.
And so they're like still bound to, you know, the process paradigm of Python on the rough side of things.
That's not true anymore. So, right. Yeah.
It would be to have something to broadcast messages between processes and even different granular servers.
So that's cool.
Yeah.
That's what I have on my table at the moment.
All right. Well, excellent.
And thanks for working on this.
It's an excellent project and it's really cool to see like kind of the innovation, like you're saying just there, you know, if it's not in Python, if it could be in Rust, like what would we change that would make that, you know, more capable, even for us?
More capable even for the Python people, right?
Yeah, exactly. And I think it's like the, I think it's like the baseline philosophy or of people like Samuel Colvin with the Pydantic project, like to, you know, to try to empower Python, like the most keeping like the simplicity and the syntax we all love about Python.
But I think it's like a very good way of evolving even the Python language.
Yeah, absolutely. You know, sometimes you'll hear people say Python is slow and then like in some sort of pure sense, that's true. But then, you know, you put it on top of things like gradient and all of a sudden it's awesome. Right. So thanks for playing your part in that.
Thank you too.
Yeah, you bet. And thanks for coming on the show. See you next time.
Thank you. Bye.
Bye.
This has been another episode of Talk Python to Me. Thank you to our sponsors. Be sure to check out what they're offering. It really helps support the show.
It's time to stop asking relational databases to do more than they were made for and simplify complex data models with graphs. Check out the sample FastAPI project and see what Neo4j native graph database can do for you. Find out more at talkpython.fm/Neo4j.
Want to level up your Python? We have one of the largest catalogs of Python video courses over at Talk Python. Our content ranges from true beginners to deeply advanced topics like memory and async. And best of all, there's not a subscription in sight. Check it out for yourself at training.talkpython.fm.
Be sure to subscribe to the show. Open your favorite podcast app and search for Python. We should be right at the top. You can also find the iTunes feed at /itunes, the Google Play feed at /play and the direct RSS feed at /rss on talkpython.fm.
We're live streaming most of our recordings these days. If you want to be part of the show and have your comments featured on the air, be sure to subscribe to our YouTube channel at talkpython.fm/youtube.
This is your host, Michael Kennedy. Thanks so much for listening. I really appreciate it. Now get out there and write some Python code.
I'll see you next time.