ORMs are one of the main tools to put first-class data access in the hands of non-SQL-loving
developers.
And even for those who do love SQL, making them way more productive.
When you hear about ORMs in Python, we often hear about either SQLAlchemy or Django ORM.
And we should, they're great.
But there are newer ORMs that take better advantage of modern Python.
On this episode, you'll meet Daniel Townsend.
He's the creator of Piccolo ORM.
A great ORM that is async first, but also has a synchronous API.
It has a super clean query syntax, and it's easy to learn.
This is Talk Python to Me, episode 328, recorded July 22nd, 2021.
Welcome to Talk Python to Me, a weekly podcast on Python, the language, the libraries, the
ecosystem, and the personalities.
This is your host, Michael Kennedy.
Follow me on Twitter, where I'm @mkennedy.
And keep up with the show and listen to past episodes at talkpython.fm.
And follow the show on Twitter via at Talk Python.
This episode is brought to you by Linode, us over at Talk Python Training, and the transcripts
are brought to you by Assembly AI.
Please check out what we're all offering during our segments.
It really helps support the show.
Do you want to learn Python, but you can't bear to subscribe to yet another service?
At Talk Python Training, we hate subscriptions too.
That's why our course bundle gives you full access to the entire library of courses for
one fair price.
That's right.
With the course bundle, you save 70% off the full price of our courses, and you own
them all forever.
That includes courses published at the time of the purchase, as well as courses released
within about a year of the bundle.
So stop subscribing and start learning at talkpython.fm/everything.
Dan, welcome to Talk Python to Me.
Yeah, thanks for having me.
I'm a big fan of the show, so it's kind of like a little dream to be on the show.
Oh, how nice.
That means a lot.
Thank you.
It's great to have you here, and you've built some really neat software that I'm looking forward
to diving into.
It's interesting because we're going to talk about your ORM, but it's also, there's so many
different areas at which now they're kind of past Python 2, and there's a lot of folks
who are saying, you know what, we can really put Python 2 behind us, and let's just build
for the future.
That just opens up so many doors, right?
Like, oh, well, type-ins, of course, type-ins.
Why not type-ins?
Well, Python 2 is why not type-ins, but not anymore, right?
And async and all these other things, and just, it's really great to have these frameworks
like yours coming along that just go, modern Python, what can we build now?
Yeah, I totally agree.
It felt like Python 3.6 kind of closed the door on the, should we still be using Python
2 conversation?
Because by that point, you had async, you had type annotations, data classes, I think
were Python 3.7, enums.
It just goes on and on.
Like, it just felt like the Python community knocked out of the park, Python 3.6 and on.
Yeah, I have a theory that is completely unsubstantiated, that actually a lot of the progress has to
do with f-strings.
So many people are like, ah, I just want to easily format this code.
What do I need?
I need Python 3.6.
Fine.
That's true, actually.
We're going to switch.
And it's such a minor feature.
Still minor.
The pain of going back would be horrible.
I couldn't imagine using Python 2 now, actually.
I know.
Yeah.
Same.
Quick welcome to some folks on Livestream.
Chris May.
Hey.
Happy to see you here.
Paul Everett.
So glad you can stop by.
And Teddy as well.
We'll get some questions in from you all, I'm sure.
Now, before we get into Piccolo and all the cool stuff that I was alluding to there, let's
just start with your story, Dan.
How do you get into programming in Python?
Yeah, so I've been programming for quite a long time now.
I might look quite youthful on this camera, but that's just because my webcam's got a filter
on it.
But I've actually been through.
The touch up my appearance is checked.
Yeah.
I've actually been.
I'm just kidding.
Yeah.
Programming with Python for about 14 years.
So Django was kind of one of my first frameworks and just really fell in love with it because
in university I learned C. And then it's like, oh, this world of Python exists and it's
a bit more easier than C. And so I've used it extensively since then.
I've mostly been working for startups and design agencies and then really fell down the
async rabbit hole over the last few years.
So I really enjoy working with WebSockets and building kind of interactive front ends and
also really kind of excited the way that Postgres continues to grow and have new features.
They're the things that really excite me in the Python world.
Yeah.
Those things are super exciting and the web frameworks are coming along to embrace them.
The language features, you know, async and await.
We had asyncio in Python 3.4, but it didn't really gain a ton of adoption because there
was, it sort of had this callback style of working, which is not the same as, you know,
await a thing and just keep rocking.
Totally agree.
Yeah.
So I think they pretty much took what Twisted had created and ported it over.
And like I say, it's all callbacks and it was still better than, it was still good
to have that option in the standard library.
But I totally agree.
Async await really kind of set it on fire because it was quite hard to approach asyncio at
first.
It was, you don't have to think about how you write code differently.
You just put the awaits in the spots and then, you know, everything kind of flows.
It's, I know it's not the same, but from how you think about structuring your code is real
similar.
Yeah.
And I think the maturity is there now with the libraries and the standard library and it
feels like you can just dive in and it feels a bit like a no brainer now to use async.
Yeah.
I think it is a no brainer for sure.
So it's interesting that you got into Django in the early days because Django is the only
major web framework that says, here's your ORM.
Here's how you use it.
As opposed to Flask that says, do what you want.
Or Pyramid that says, do what you want.
You know, you could use an ORM, but you don't have to.
But Django is, it's part of the culture and the zen of it.
Right?
Yeah, I'm a big fan of Django.
I think it's kind of a masterpiece because it stood the test of time so remarkably well
and how you could have written a app 12 years ago and then in an afternoon, you could have
upgraded it to the latest version.
So there's a lot to be said for Django and I really like the tight integration.
So people tend to prefer one or the other, the kind of the Flask route where you can make
all of your choices.
But I like really tightly integrated solutions where you come to a problem and then you pretty
much know what to pick.
Yeah.
So that's definitely inspired Piccolo to some extent.
Yeah.
I love the diversity of technology and libraries and ways of doing things in the Python space.
But when people come from other technologies, it's a challenge to go, well, here's 20 different
ORMs you could use and seven different web frameworks.
And they're like, I don't want seven.
I want to know what I should be doing.
What should I do?
Right.
And so it's a mixed bag.
I really like that.
But the thing is, if you come from the JavaScript world, then it's a relief because you've only
got 20, not 2000.
Oh my goodness.
This one's seven months old.
It's like ancient.
Can we still use it?
Yeah.
No, no.
That's a whole different discussion.
The JavaScript churn over there.
Yeah.
But yeah, let's focus in on what you built here.
So let's talk about Piccolo.
You just sell it.
You describe it as a fast async ORM for Python.
That's easy to learn.
And I think it seems like it really hits on all those points.
Tell us about it.
Yes.
So all those terms are like fairly aspirational.
So the fast is because it's built on asyncpg.
So this is a really fast Postgres adapter that the Magic Stack guys built.
Part of the reason it's so fast is it's written in Syphon.
So it's compiled.
And then there's also a few other features in Piccolo, which kind of help it speed wise, like
frozen queries where it doesn't have to generate the SQL each time.
It kind of caches some of it.
So that's kind of where the fast comes from.
Async.
Oh, nice.
Async.
Async.io.
That was really one of the core reasons for building it is because I was a Django channels
power user and I had to build a lot of chat applications in my day job.
So for me, async was kind of essential.
And then the easy to learn is called also aspirational.
I put like as much effort as I possibly can into the documentation and stuff like that.
But I'll let the readers be the judge of that.
Yeah.
That's in the eye of the beholder.
Right.
But at the same time, you have done a lot of things that I don't see others doing.
Like the documentation is good.
Obviously, that's pretty standard on a lot of the different frameworks, although they can
be super intense.
Right.
Like I love SQLAlchemy.
But when I go and read there, I'm like, OK, here's a 20 page doc.
I better pay attention and turn off my music and just like not miss something on the other
hand.
But what you are doing that's pretty unique is you've got like a playground.
You've got a playground for the admin backend.
Oh, by the way, there's an admin backend.
And you've got a playground for trying out queries and stuff like that.
Right.
That's pretty unique.
Yeah.
So I was really excited by the playground concept.
So it's something I borrowed from the Swift world.
And because Apple are trying to make Swift accessible to newcomers, they have this concept
of a playground, which is basically a pre-populated chunk of code.
And then you can just play around with it as kind of the name implies.
So I use IPython to achieve it.
So a lot of people know IPython, just running it on the command line like a terminal.
But you can actually embed it within your Python code.
So the way the playgrounds work is it creates a SQLite database.
It loads an example schema, populates it with data, and then basically launches the IPython
shell.
So you basically have all of that set up.
Because one of the problems with learning an ORM is the barrier is huge when you think
about it for a newcomer.
You have to set up a database.
You have to understand schemas and migrations and running them and populating data.
So I just wanted to say, look, I'm going to shortcut all of that.
You install Piccolo.
You do Piccolo Playground Run.
And then you've got an example schema.
And then you can actually follow along with the documentation.
So whenever there's an example query in the Piccolo docs, you can actually run it in the
playground.
And you should see the results.
So yeah, I was quite excited about that.
I like it a lot.
And you're right that it is challenging.
You know, as we build up experience and we get used to it, it's like, oh, yeah, you just
fire up the database server and then you connect to it.
Then you do this thing.
But how do you get the database server installed?
What if you have the wrong version?
What if it requires authentication?
What if there's a firewall?
Just like there's layers and layers of places where people get sheared off.
Like, ah, this doesn't work for me.
All right.
And so keeping that simple, it's great.
And I think I just want to give a shout out to SQLite.
Because what you just said is such a good example of you're probably going to run this
against Postgres or something like that.
Yeah.
But for the little example, you don't need that.
You can just use SQLite.
And it's so nice that that's a serverless in the sense that it's just embedded.
Doesn't require a separate process to run or connect to.
It's already comes with Python, generally speaking.
It's really nice.
Yeah, I totally agree.
And that's the reason for SQLite support within Piccolo.
Because as you said, no one's going to run it in production.
But then it's just that frictionless setup, which is so great.
Like, using SQLite asynchronously doesn't really make a lot of sense.
Because you don't really have any network lag talking to the database.
But it's great to have it in there for convenience.
Yeah, it is.
You know, I suspect there may be some people who run it in production in like an early, early
prototype stage.
Yeah.
They just want to get something up.
And here, let's just put it up on, you know, on some hosting place and just check it out
real quick.
And then we'll go from there.
Yeah.
One thing that's quite nice is I used to do this in the design agency.
We'd use SQLite for the prototype.
And then you could R-sync it down to your local machine.
So you didn't have to do a database dump and reload it.
You could literally just R-sync the file.
Yeah.
So there are a bunch of conveniences with SQLite for sure.
Yeah.
So it gets to be too much data.
Or really, you want sort of too much concurrency or multi-machines scale out and all that sort
of stuff.
So it's not the final destination.
But I do think it's a really interesting and important starting ground there.
Yeah, for sure.
Yeah.
So I gave a shout out to two ORMs already, the Django one and SQLAlchemy.
And I feel like those are probably the two big hitters of the Python space, although there
are many, many more.
So why not just use those?
Why go and create Piccolo?
Yeah.
So when I started it, there was no async options for ORMs at all.
It was all very new.
And my day job involved async programming all of the time.
Because of chat apps, online games, that kind of stuff.
So that was really why I was like, well, nothing really served my needs at the time.
Right.
Because until recently, SQLAlchemy had no async capabilities.
Yeah.
And Django ORM more or less still doesn't, right?
That's the final part that they're trying to move to async.
Yeah.
So the workaround is to run it in a thread, but it's just not as performant as running
with asyncio directly.
So there's kind of this vacuum for a time with async ORMs.
But then it was also about just starting through a blank sheet of paper and thinking, look,
like we've got all these great tools to work with now in Python 3.6 and onwards.
How can I absolutely push them to the limit?
So the syntax, it uses a lot of direct references to objects rather than strings.
So for example, in Django, you do my table to objects and then dot filter and you name equals
Dan.
The problem with that is you can quite easily make a mistake as the code changes.
So you might end up having a column which no longer exists or so strings can be a little
bit fragile.
So with Piccolo, it's just object references everywhere.
So rather than name, it's in this example here, it's band dot name where band is the
name of the table.
Right.
So it's like many of the ORMs, it's sort of class OOP based, right?
So you create a class that maps to a table and database, right?
Yeah.
So there's one distinction I'd like to make quickly as well.
So there's ORMs and then there's query builders.
And in Python, there's not really too much of a distinction, but in other languages like
JavaScript, query builders are really popular as like a separate idea to ORMs.
And query builders, they kind of, you create a SQL query, you execute it on the database,
and then you tend to work more with dictionaries and lists rather than objects.
So I'd say that 90% of Piccolo is a query builder, but people are used to ORMs and Python and it
can actually lead you to some quite poor patterns, ORMs, because what the first thing people learn
with Django is they'll go my table dot objects dot get.
So it returns an object and then they'll change the attributes of the object and then they'll
call save.
But then that's actually two database queries because you have to get it and then you have
to update it.
Well, and it's not just two database queries.
It can be a lot of serialization.
Yeah.
Like the slowest part of all of my data stack is if I were to try to pull back 10,000 records
at a time, it's the deserialization of those 10,000 things that is actually the slowest part
of the entire process.
And here, if what you're talking about, like you're talking about the band, if the band
has tons of information and you just want to change the name, right?
You're pulling all that data back, converting it or whatever, then changing the thing and
then pushing it back down.
Yeah.
It's a lot of work.
And a lot of the time what will happen is you'll serialize it into an object and then
you'll deserialize it out into JSON anyway.
And it's kind of pointless when you think about it.
And there's also the problem with objects getting stale.
So you might pull an object into memory, but then some other user might manipulate all those
objects.
But then when you save it, are you going to overwrite those fields that have been updated
in the database?
So it's problematic in a way.
So it is an ORM, but then I'd really encourage people to look at it as a query builder too,
because in my own apps, I use the select method a lot.
So rather than returning the objects, it returns dictionaries.
And then it has this option, an output method.
So you can just literally deserialize it straight into JSON.
So this is what makes it fast because the query is going through asyncpg, which is super
fast.
And then it's coming back as a dictionary straight from asyncpg.
And then it's using all JSON to stick it straight into a string.
So you're kind of missing, you're skipping all this deserialization nonsense.
Yeah, yeah.
This is super interesting.
Now that you pointed out, I'm not used to seeing projections in ORMs.
What I'm used to seeing is give me back the classes that, in this case, a band, right?
Do a query like where the popularity is greater than 100 or whatever, and give me a whole bunch
of bands, band objects back, and then I'm going to work with them.
But sometimes like the one I work with most commonly is Mongo Engine.
I know it's not an ORM, it's an ODM, but close enough.
Does the serialization bit.
And you can say, I don't really want the other parts.
Just give me the name, like the website or something.
And it won't ship or deserialize those things.
But you just end up with the same objects that just have none everywhere else, right?
Where in your ORM, you can actually say band.select, band.name, band.url, or something like that.
And that would return the dictionary with those two things, right?
A list of dictionaries, probably?
A list of dictionaries, yeah.
And then it also has a feature, like Django has this feature, which is invaluable.
It's called values list.
And so if you just want one value back, it'll then condense it down to just a list of values.
Right.
Like I want all the IDs of the bands that play pop music or something.
Yeah, and it's so much more efficient than doing like in Django, you know, band.orl,
objects.all, and then looping through to get the IDs.
It's just these little tricks you learn.
Like if you use Django for ages, you just learn these little tricks.
Right.
Another one that stood out to me is the ability to do set-based operations.
Because when I think of ORMs, I just, for everyone listening, I adore ORMs.
I think they're really empowering for people.
I think they take a lot of the modern tooling that we love, like refactoring, and allow you
to apply that like over to your query.
Because if you wanted to like change the casing of band.name, you could refactor, rename that,
and it would affect your queries because that's still Python code, right?
That said, there are places where people either abuse it or it's just inappropriate.
So the places where it gets abused a lot would be the N plus one problem, right?
Where you've got a lazy reference to something else.
And you don't know that that's going to be a separate query for every time you touch one
of those.
And you get a list of objects back and you loop over them and you access like, in your example,
you've got band.manager.
For B and band, band.manager, right?
That could be 101 queries for what should have been one, right?
Yeah, that's a really good point.
And even experienced developers get this wrong because they might use serializers, which are
calling properties under the hood on the table, which are triggering SQL queries.
So this is another design intent behind Piccolo is whenever a query is run, it's very explicit.
You are literally calling .run or .runsync.
So there's none of this magic.
You can't accidentally create an N plus one query.
You might accidentally end up with a coroutine or something.
Yeah.
So yeah, it's a really good point because I think N plus one is kind of like the scourge of
developers with performance.
And also coming back to your point about ORMs, as a backend developer, we can spend hours a day
using ORMs.
It's kind of like one of the main tools in our tool belt.
So it's kind of quite nice to start from a blank sheet of paper and think, how can I make
that experience maybe like slightly better if I can?
This portion of Talk Python to Me is sponsored by Linode.
Visit talkpython.fm/Linode to see why Linode has been voted the top infrastructure
as a service provider by both G2 and TrustRadius.
From their award-winning support, which is offered 24, 7, 365 to every level of user,
to the ease of use and setup, it's clear why developers have been trusting Linode for projects
both big and small since 2003.
Deploy your entire application stack with Linode's one-click app marketplace, or build it all from
scratch and manage everything yourself with supported centralized tools like Terraform.
Linode offers the best price-to-performance value for all compute instances, including GPUs,
as well as block storage, Kubernetes, and their upcoming bare-metal release.
Linode makes cloud computing fast, simple, and affordable, allowing you to focus on your
projects, not your infrastructure.
Visit talkpython.fm/Linode and sign up with your Google account, your GitHub account,
or your email address, and you'll get $100 in credit.
That's talkpython.fm/Linode, or just click the link in your podcast player's show notes.
And thank them for supporting Talk Python.
The N plus one problem, I believe, is either there's some tool doing something behind the
scenes you don't know, but often it's just a lack of understanding.
Oh, that actually is a lazily loaded property, which is going to trigger a query, so I should
have put a join, and then I'd be in a better place.
That's a programmer pattern thing that you should pay attention to and work with.
The one where I don't know how to fix it is more like the serialization thing.
Like, what if I want to go through my database and go to 10,000 records and make some changes
to them?
So often it's do the query, loop over the 10,000 things, make a change, call save.
Maybe it's in one giant transaction that you finally push the changes back, but you're
pulling all the data back.
And one of the things I really like about your, or I'm here, is like this update section here,
where you can do set-based operations without pulling the records back.
Yeah.
So you can do stuff like, so this example here, wait, band.update, band.popularity, 10,000.
But then you can also do band.popularity is band.popularity plus 10.
And then in the database, it will then just add 10 to all of the numbers.
Oh, really?
That's awesome.
And then it's all just magic around, you know, Python magic methods.
It's just, as a library author, it gives you so much power.
It's one of the things I love about Python.
Yeah.
Because when you're building like query languages, like ORMs, I think very few languages can
really rival Python with its flexibility.
Yeah.
So that's really why a lot of this stuff's possible.
It's really neat.
And I think the ability to push these changes without actually, you're still programming
in this, the ORM classes and the models, but you're not actually pushing a whole bunch of
them back and forth to make the changes, but to do these set-based operations, like delete
them where, or make this update to this value where this is true, and then just push that,
make that happen in the best way you can with SQL, right?
Yeah, exactly.
I think coming back to your point around the M plus one, I think properties are something
that can be a little bit evil.
And I've really shy away from them in the Piccolo code because it can, you call a property
and you think you're getting a value back, but it could be doing any kind of magic.
And then once you've defined something as a property, you then can't add arguments to
it without breaking your API.
So yeah, I think that that's something I've tried to steer away from in general with Piccolo,
like properties.
Yeah.
A lot of hidden stuff happening there, right?
Yeah.
It's not entirely clear.
I think they're super useful, but certainly in something where I thought I was accessing
a field of the class, but what I actually did is make a network call.
Like that distinction is possibly too big of a bridge to just make that automatic a lot
of the times.
Yeah.
There's no async properties in Python as well.
So that's kind of one of the reasons why it doesn't use any async properties.
Maybe they'll add it.
If they add it, maybe I'll put a comment on the PEP saying, don't do it.
Yeah, exactly.
Another thing that's interesting here is on all this code, every code you've written
is await band.select or await band.delete and so on or update.
And then at the end, you say run.
This is the explicit part that you were talking about in your API.
Like I know here's where it's happening.
And it probably makes a lot of sense to do that as well, because on the flip side of it,
that's where you have to await it anyway, right?
Yeah.
So what happens is you build up this query, you just chain methods to it.
And then at any point you can print out that object and I'll give you the SQL.
And then until you actually await it, there's something under the hood that I don't really
publicize.
You don't need the dot run.
If you await it, it will run as a convenience because people forget.
But then it just makes it easier from a documentation perspective to say when it's async, use run.
And when it's synchronous, use run sync.
Right.
And then if you do run sync, then I've got like a bit of magic in there where it tries to
create an event loop to run it, or it tries to figure out if there's already an event
loop.
If there is, run it in there.
So you can use Piccolo in an old school WSGI app if you wanted to just synchronously.
Yeah.
Well, let's dive into that because that's one of the things that really stood out to me.
Many frameworks or APIs packages tell you, you're going to either have to go, you know,
you're going to take a fork in the road.
You're going to go down the async fork and you're going to use the async library like
HTTPX or you're going to go on another fork and you're going to use the request library
that has no async.
And you're going to go down that path and you choose and then you just go.
And with Piccolo, you can actually run, I guess the default behavior would be to be async and
await.
But it has this dot run sync, which will kind of cap where the asynchronous behaves and goes.
And it'll just that you could run it in a regular Flask app or Django app or whatever
and not worry about it being async at all.
Right.
Yeah, that's right.
And it's actually one of the design challenges with Piccolo is how do you create an API which
is synchronous and asynchronous?
And there's only really two ways of achieving it is with a method like run or run sync or
with context managers.
So some of them you'll create either an async context manager or synchronous, and then that
will then impact whether the underlying query is synchronous or not.
But then it adds a little bit more boilerplate if every time you're in a query, you need
a context manager to tell it to be async.
So this is kind of the best outcome I could think of was just have dot run or dot run sync.
I think this is great, especially since even if you forget the run, it'll still run async,
but there's a way to kind of cap it.
So something I wanted to talk about, it's driven me crazy ever since async and await were introduced
because I don't find it to be true, but I hear it all the time spoken about in the community
async and await, they're super neat, but they're like viruses.
And in the sense that soon as like one of your functions way, way at the bottom has to do
something async, well, then the thing that calls it has to be async and await it.
And the things that call that function now all have to await it.
And that percolates all the way to the top of your app.
And so now you've, by using any async library, you've turned your entire thing into this like
async vertical call stack.
Your example here shows that that's not, it doesn't have to be that way, right?
That's sort of the naive, I'm just going to like write the code without thinking about it.
But if you wanted to say, have your data access layer do three things, because it's got to pull
some stuff from different places.
You want that to be async.
It doesn't mean that function has to be async.
It could just start its own event loop, do the three things faster than without it,
and then return the answers, right?
You can kind of cap it at any level that you want.
And your run sync is kind of an example of that.
Like you can choose to not have it, just turn your entire app async.
You can jump between them.
So typically if people use an async, then it's like, the argument is, if you need async,
your whole app probably should be async because otherwise why use an async?
But then you can flip between them quite easily.
So if you've got a synchronous app and you want to call some async code,
there's asyncio.run.
And you can also do stuff like spin up an event loop in a different thread and then send
work to that.
Yeah, absolutely.
So it is quite fluid.
You can flip between them quite easily.
Yeah, I mean, just one example that comes to mind is what if I wanted to go web scrape
every page at a certain domain?
So I've got a function that gives me a domain.
I give it a domain and then I want it to return or store into the database all the pages, right?
That would be perfectly reasonable to have that thing go, okay, well, let's do a,
a request, figure out what all the pages are, and then just, you know, recursively sort of grab them
all asynchronously.
You would get a huge boost on getting every page off a site, even if that function blocked.
You know what I mean?
Because it itself could just go crazy against the server.
Maybe it shouldn't, but it could.
Yeah.
I'm a huge fan of asyncio.gather as well.
So that's a really beautiful API just for saying, do these 50 things now, please.
And let me know when you're done.
Yeah.
And block, right?
When you're done, give me a list of answers or errors.
Yeah.
Also out in the live stream, Chris May.
Hey, Chris.
It says, I'm so excited to use Piccolo with unsync.
I have a workflow that'd be nice to parallelize.
And yeah, so I think unsync is another really interesting library that wraps up asyncio plus
threading plus multi-processing.
But then gives you a nice way to cap it as well because you can go, like, be given a task that
comes back from there.
If you just ask for the result and it's not done, it'll just block like a regular thing.
And it does kind of what you're talking about.
It'll have a, it has a background thread with its own event loop and it just pushes all the
work over to there.
Yeah.
So, yeah.
That's a cool library.
Yeah, it is.
At least for the size, right?
Like 126 lines and it unifies those three APIs and adds some more stuff.
That's pretty big bang for the Python byte.
Yeah, that's impressive.
I wish Piccolo was that terse.
I think thousands, tens of thousands of lines by now.
Yeah.
So another one of the new Python 3, 6 onward type of things that's really cool is the type
annotations.
Yeah.
I love type annotations.
So part of my day job in the past was using Swift and Swift is almost like hyphens, like
brother or sister.
It was very heavily inspired by Python.
Right.
It's like if Python all of a sudden decided it was incredibly strict about typing,
and type definitions.
Yeah.
That would be a lot like Swift, right?
Inward compiled.
Yeah.
It's a combination of type annotations and the tooling to support it.
So in Python's case, it's VS Code.
And in Swift's case, it's X code.
And it just means that when you're refactoring, it just makes it so much more confident about
what's going on.
Yeah.
It provides documentation because previously people were putting in a doc string anyway.
So why not put it in your function definition and then you can introspect it.
And my Pi is incredibly powerful.
I honestly don't think I could have built Piccler without type annotations because it makes your
code so much more maintainable.
The click to go to VS Code as well is just a beautiful usability improvement.
And then kind of one of the hidden benefits is it makes tab completion so good.
So a lot of Python auto completions, they use a library called Jedi under the hood.
So when I was building Piccolo, I had to look at the source code to try and figure out how
it does its magic.
And if you give a type annotation to something like this is what this function returns, it's
a really strong indicator to Jedi that this is what's going to get returned.
I don't need to do any magic anymore.
Yeah.
I would say that half the time, that's why I do it is to make the editor better.
So both VS Code and PyCharm, like take a good look at what the type annotations are.
Yeah.
Right.
And you just say, oh, you're trying to pass a string and that's really supposed to be
an integer.
But then also, like you say, tab completion or autocomplete all over the place is fantastic.
Yeah.
I think there's a distinction as well, where I think if you're building an application,
let's say you're building a Django app or a Flask app, you don't need to care quite as
much.
Like I personally would still add type annotations, but for libraries, I think it's just absolutely
essential.
Like I don't think any new Python library should be written without it because you kind of,
shortchanging your users in a way.
Is there?
I totally agree.
Yeah.
In the Flask app, for example, that you've mentioned, you know, I would say on the boundaries,
right?
Like here's a data access layer, like put type annotations on those little bits there.
And then the rest of the app, usually the editors and the tools will just pick it up
and continue to tell you what you're working with.
Yeah.
But if you're doing a library, right?
You want every function or every class to be kind of standalone and know everything it
can.
Yeah, definitely.
And one more thing about type annotations is it's probably the greatest source of interview
questions ever made because you can ask people in an interview, what's the difference between
like a sequence and an iterable?
And like when you use type annotations, you really start to think about what's going on.
And it's a great learning experience too.
Yeah.
I want to pass a generator here, but it takes a list of things and it says that won't work.
And maybe you just need to relax your type annotations to an iterator.
Yeah.
A thing.
A quick question from Roller out there in the live stream.
Hey, Roller.
Can I just pick a little and play some Mongo engine or is it just for a relational stuff?
Yeah.
It's just relational and it's, you can use SQLite locally, but it's mostly Postgres.
It was really built to take advantage of Postgres because Postgres is, it's like the fastest
growing SQL database in the world, which is remarkable to think it's how old it is.
And I think I'll, you know, I think adding other SQL databases would be quite easy, but
adding something like Mongo would be a bit trickier.
I wouldn't say it was impossible, but a bit more work.
Yeah.
I would think so.
Yeah.
It's not, certainly not impossible, but like joins and stuff would get tricky.
What about SQL injection?
I mean, many of us have heard about the little Bobby tables, XKCD, which is delightful, you
know, you know, sort of schadenfreude sort of way.
We all kind of want to relish in somebody else suffering this, but I find that this is actually
one of the really nice things about ORMs most of the time is that they scrape off the ability
to do SQL injection because you're not building the SQL.
Yeah, definitely.
So your database adapter.
So something like asyncpg or psycopg in the synchronous world, what you want to do is you
want to pass it the query string with placeholders for any user submitted values.
And then you submit the values that separately, like in a list.
Right.
Like a parameterized query, basically.
Yeah.
And as long as you do that, you're safe.
But then for a library, when people are programmatically creating very complex SQL queries, and then you
need to try and make sure that you've got the right values that match the right placeholders
to pass to the adapter.
It is quite challenging.
There's some like recursive code where it has to.
So we use something called query strings internally within Piccolo.
So it never concatenates strings for SQL statements.
It just, it uses query strings and then it compiles them before sending it to the database adapter.
And it basically looks through all of the sub query strings it might have if it's a really
complex query.
And then it kind of passes it to the adapter.
But yeah, it's just one of the complex things about building ORMs for sure.
And also one of the most dangerous to get wrong.
Yeah.
Yeah, it absolutely is.
There's untold number of bad things that can happen with SQL injection.
And it's so easy.
All you have to do is put a single little tick to comment out stuff, semicolon to finish that
statement.
And then you can run arbitrary code.
And a lot of times you can even, some database engines will let you run sub process type things.
Which is even worse.
But yeah, it's not good.
So you definitely want to avoid it.
Yeah, it's a good argument for using ORMs and query builders because it'll make it less
likely.
I think for sure.
You know, another thing that I wanted to touch on a little bit here is the actual filtering
or projection statement type bits.
So I mentioned using Mongo Engine before, which I'm a big fan of.
And it's basically a Mongo equivalent of the Django ORM.
So in that regard, they're real similar.
And you do things like, if I wanted to say where the band popularity, or let's say the
band name is Pythonistas, right?
You would just say name equals Pythonistas as part of the filter.
And there's two things that are crummy about that.
One is you get no autocomplete that there's a column called name because it doesn't really
know what class, even though you started out like band.objects.
It doesn't, in the filter part, it no longer knows that the name came from the band, right?
That's not part of the language.
And then the other one is you're doing an equals or you've got like weird operators like in
the name, like name underscore underscore GT for greater than and stuff like that.
Whereas yours, you just write what you would put into an if statement or a loop or something
like that.
So you would say like band.popularity less than a thousand.
That's the thing you send in there.
Yeah, that's right.
So I've been caught up, tripped up so many times in the past with Django where I've had
something like name double underscore something else.
And then it can't really understand that's wrong, like a linter or when you, while you're
coding, it only really knows at a runtime and then you've got a 500 error.
So the idea here is a linter would be able to pick up these problems.
Exactly.
Because so much of the pieces are, they're just using star, star, kwrgs and then they
figure out how to generate a query out of like looking for special keywords in the key names
and then turning those to columns.
Also the refactoring thing, right?
The linters.
And if I want to do a refactoring to rename popularity, it's not going to check popularity
underscore GT as a keyword argument.
It has no idea those are related.
Yeah.
Yeah, definitely.
So the way it's implemented this, the double equals and all these operators is the amazing
things about Python is how you can just overload like fundamental things about the language.
So you can overload what addition means.
And when someone first tells you that, it sounds like the most mental thing in the world.
Cause why would you want one plus one to equal five?
But then it turns out when you're building an ORM, it's golden.
And this is one reason why I find Python just so compelling over and over again is because
as a library author, you can do this stuff that you can get closer to more of like a DSL than
like a normal programming language.
Specific language.
Yeah, absolutely.
Yeah.
So is this a be done with descriptors or what's the magic?
For the less than there's like done like double underscore LT and you can override that.
And then what happens is when you call that method, it returns a where object.
And then you can also, you could do in brackets, band dot popularity, less than a thousand and
then double and sign as well.
Band dot popularity greater than 500.
So you can combine them with and and all statements.
So the where statements in Piccolo can get like really powerful.
So you just have to teach the where clauses how to and and then structure it in a way that
Python will let it kind of go through, right?
Yeah.
Or you can do dot where, put some stuff and then another dot where statement.
And if you've got multiple where statements, it becomes like it's an and.
Yeah.
But yeah, it's just, it's just all Python magic, which is one of the reasons I love Python.
Yeah.
Speaking of overridden things, the thing that I think is the most insane, but I'm starting
to love, but took a while to get used to is the way the path object works for defining
paths.
Yeah.
So, you know, the forward slash often means like drive separation on the POSIX systems.
And, you know, it's close enough.
You could actually put forward slash in your strings and Python on Windows and it'll
still like, okay, fine.
Backslash is what you meant.
So they overrode the divide operator in code to allow you to concatenate strings and paths
together.
And that's just crazy.
Yeah.
The first time I saw it, I was very confused, but when you understand that it's okay, but
yeah, totally.
Yeah.
I'm, I've gotten okay with it as well.
And I start to use it and I really like it now, but I was like, I don't know if I can
get behind.
This is, this is a bridge too far.
This is, that's division.
What are we doing here?
No, it's cool.
It's cool.
Talking of magic to Piccolo, like all around, it uses meta classes a lot.
And there's something that got added in, I don't know if it's Python 3.7 or 3.6, but they
actually changed meta classes slightly.
So there's now like a Dunder magic method called init subclass.
And Piccolo uses this a lot.
And it's, it's actually an amazing hidden feature of Python where you can now add keyword
arguments to a class definition.
So if you had class foo, open brackets, inherits from bar, comma, and then you can start adding
keyword arguments to the class to customize its creation.
And that's kind of like a new layer of magic that's been added to Python recently.
And Piccolo uses it extensively, but I don't see many other libraries using it yet because
it's probably not so well known, but yeah, just kind of spread that bit of magic.
So hopefully people can use it too.
Nice.
Yeah.
That sounds awesome.
I can certainly see how I'm like trying to create the classes, like the band class or
whatever you say that it's going to be, would definitely use that.
So one of the things that you say is awesome about Piccolo is the batteries included.
Yeah.
So let's talk about some of the batteries.
So yeah.
So the main battery by far is the admin, because when I started it, I was working for a design
agency and admins are incredibly important for design agencies because you want to put
something in front of a customer that they like the look of and they're comfortable using.
So this is a huge part of the effort that's gone into Piccolo.
And so this is a, it's sister project called Piccolo admin.
And what happens is it's, it's an ASCII app.
So I can maybe go into more detail about ASCII later on, but all you do is you give it a
list of Piccolo tables and then it uses Pydantic.
So Pydantic is a serialization library and it basically creates a Pydantic model from the
Piccolo table.
And then Pydantic models have this really useful method where it's JSON schema and it creates
a JSON schema for the Pydantic model.
Right.
Because the Pydantic classes know this field is an int, this one is an optional date time
and so on.
Yeah.
So Piccolo has really good Pydantic support, but it's in a sister repo called Piccolo API.
And then that, so that creates the Pydantic model and it also has something called Piccolo
CRUD.
So you give it a Piccolo table and it creates another ASCII app, which has got all of the CRUD
operations for your database.
So you can programmatically create this huge API just by giving it a few tables.
And then the front end is written in Vue.js and it's completely decoupled from the back
end.
It's just all by API.
So yeah, I'm a huge fan of Vue.js because it's, it's very natural for Python developers
who are used to the template syntax in Django and Flask.
If you looked at the Vue templates, you'd be like, this looks very familiar.
So I'm a big fan of that.
Yeah, it's nice.
Yeah.
I think it's super close to Chameleon because of the attribute driven behavior as well.
Yeah.
But then like to make a working admin requires so much work because you've also got the security
side.
So Piccolo API has a bunch of really useful ASCII middleware and it has like session authentication,
CSRF protection and rate limiting as well because you don't want people to spam the login.
So like just to get a fairly simple admin is in, it's like a iceberg to do it properly.
So yeah, a lot of efforts gone into the admin, but I'm really proud of it.
And like, this is really what excites me more than anything about the future because as
we add support for post GIS and stuff like that, being able to create really interesting
widgets around data.
So, you know, how can I design a rectangle field for post GIS or a location field or.
I could see some really cool stuff that are sort of template extensions, you know, like
let's just pick Jinja, for example.
Like if you had one of these objects, you could pass it over and it knew, for example, here's
a daytime, you could just say, make a calendar picker here.
And it just, you know, as long as you have the JavaScript included and instead of just
putting the text, it gives you like a nice little Ajax-y widget or these, this list goes
on a map, drop in the map widget and off it goes.
Yeah.
So basically Piccolo admin, it's just, it's just turns Pydantic models into UI and it's
actually quite, it's actually quite interesting.
And I'd love to get it to the point where for a business app, you just use Piccolo admin,
you don't even have to build UI.
You just say, here are my tables.
Yeah.
And then, yeah.
Well, the truth is so often, like there's a lot of these little internal apps that people
build that are just like forms over data.
I just need to see the details, click on one, edit it, create a new one and delete one.
And like, that's the app I need for this table.
Could you build that?
Right?
Yeah, for sure.
It is a lot of it.
Cause I think with the approach I talked to Piccolo is you have a lot of Python libraries
and they kind of start from the outside in.
So they start from the URL layer and then the views and like middleware.
And then over time, they then add the data layer and the security.
But then with Piccolo, it's kind of from the inside out.
Like I started from the data layer and then have the admin and some middleware.
So it's quite a nice companion to have a ASCII app.
So just kind of pick the framework you like and then Piccolo kind of gives you the data
layer and the admin.
That's kind of.
Yeah.
Well, let's talk about the ASCII stuff a little bit.
Cause you did mention that there's some interesting support for those things.
And it's got like, to some degree, native FastAPI, Flask, and even Blacksheep, which is
an interesting one, support for those frameworks, right?
Yeah.
So I'm a huge fan of ASCII because I was a Django Channels power user and Andrew Godwin
created ASCII out of Django Channels.
It's really like a beautiful thing.
If you look, so Starlette was the one that really built the foundation.
So this is an async library, but also kind of like a framework.
Like you can build an app with Starlette or you can use it to build up frameworks.
What's amazing about ASCII is like every component in an ASCII framework is ASCII.
So ASCII is basically the spec where it's a function that accepts a scope, a send and
a receive.
And then if you look at the internals of Starlet, everything's ASCII, like the middleware's ASCII,
the endpoints are ASCII, like the whole thing.
And it's like super composable.
So you can say, I've got an ASCII app and you can mount other ASCII apps within it.
So this is what I love about ASCII as a spec.
So you can say, take a FastAPI app or a Starlette app and include Piccolo admin.
Same with Blacksheep.
Right. You could say slash catalog is actually handled by this other app written in FastAPI
where the main thing is written in Flask or something like that.
And you just kind of click them together in that cool way.
Maybe Core would have to be the one, but still.
Yeah. And I really love that.
It's quite exciting that you could kind of build an app with multiple frameworks and be like,
well, this part of the app will be better served by FastAPI.
But this bit, I just need Starlette or I want Blacksheep or whichever ASCII framework people
can dream up.
So I think it's really exciting for the Python community, the ASCII spec.
Yeah. And we did talk a little bit about the challenges and the cascading effect of async and await.
But if you're already running a framework that has async view methods, there's just nothing to it, right?
You just write your code and you just await the bits you got to await in the view method.
And it just like the server and the framework handled the vent loops and all that business.
Yeah. I think as well, what kind of happened is asyncio came out and it doesn't directly affect speed.
It's more about throughput, but it's like the Python community took it as a challenge to build faster frameworks.
And so there's a lot of them have really quite fast internals and they do feel quite cutting edge.
Yeah. Like UV loop and stuff like that.
They're like, how can we do this, but have the minimal overhead of adding this?
I mean, people do talk about, okay, async and await won't make your code go faster.
Well, it won't make CPU code go faster.
But so often what we're doing, especially in web apps is waiting.
I want to wait on a database and then wait on the network.
And then I want to wait on the database again.
And then I'm going to wait on an API and then I'm going to send back three lines of JSON, right?
Like 99.9% of that is just waiting on something else.
And when you're using async and await, like you can just do other things while you're doing that 99% of waiting.
Yeah. So usually if you do a database query, it takes a few milliseconds.
But then if you use the time it module on Python and you see how long basic operations take, they're more like microseconds.
So there's like orders of magnitude difference in how long a database query takes to basic Python stuff.
But this is why having stuff like UV loops are really important because if you had a really slow event loop, it kind of wouldn't make much difference.
But because the event loops fast as well.
And like a lot of the projects I did in the past, the throughput is really important because some apps, you won't have a lot of traffic.
And then all of a sudden you'll get a thousand users.
So I was doing like live events and you'd get a thousand people at once.
And in that situation, throughput is incredibly important.
Right. I mean, look at how the whole healthcare rollout in 2008 went.
Right. I just can't help but think there must have been more awaits available to those, that those frameworks and those web apps.
Because it just kept crashing and stuff was timing out.
And I'm sure it's just like, well, we're just going to wait on this other slow government API.
And we're going to do it for a lot of people.
And it's just going to overwhelm it.
Right. And it's just crazy.
It just feels natural because like in the web server world, you had Apache and a lot of people moved to Nginx.
That's very similar.
It's like event loop driven.
And we've kind of seen how beneficial Nginx was.
So it just makes perfect sense to build your backend in the same way.
Yeah, absolutely. Absolutely.
All right. A couple more things we got some time to talk about.
So over here, you've got a Django comparison page, which I guess also could be slightly a Mongo engine comparison page.
Because like I said, they're basically the same thing without the nesting.
So if somebody is familiar with Django and they're like, I would like to consider using this for my framework or for part of my code or whatever, just like want to use it.
But they already know how to do stuff in Django.
You have like, well, here's how you would create an object and save it in the different frameworks.
Here's how you would update an object and make changes and so on.
And you can just go through one at a time and just sort of compare the different pieces, right?
Yeah.
So it's quite heavily inspired by Django.
But then I think Django in its ORM, it's more Pythonic.
So rather than using where it uses filter.
But then with Piccolo, it's meant to be very, very close to SQL.
Because the theory is, if you know SQL, it'll be super easy to learn.
And when you do need to drop down to a SQL query, there's no like mismatch.
You're just like, well, I'm always sort of working in a SQL mindset.
But there are a lot of similarities with Django still.
I think people could pick it up quite quickly.
Yeah, I agree.
Like instead of object that values list, you have a select projection.
Or instead of filter, you have a where.
But it's honestly not a huge mental jump to make.
Yeah.
And Piccolo also takes like huge inspiration from Django migrations,
which I think is kind of like the gold standard of migrations in any language.
So a lot of effort's gone into that.
Yeah.
That's another one of the batteries that you were kind of touching on, right?
Is the migrations bit.
Yeah.
But it's migrations are incredibly hard to do right.
I think the Django way is.
I can only imagine.
I don't even want to try to imagine writing that because it seems really hard.
Yeah.
So the way Django does it is it looks at your tables and then it creates a migration file.
It then adds up the migration files to build a picture of what the schema looks like.
And then that's how it then creates subsequent migrations by doing a diff.
Right.
That's what Piccolo does.
I've got to go from this level to that level.
So here's the five migrations to basically use in order.
Yeah.
And then you've got to do code generation as well.
So like with Piccolo, it has to actually create a Python file.
And that's harder than what it seems actually writing a Python file.
But if you look at the Piccolo migrations, they're actually really, they look like quite beautiful
Python code.
And there's a little trick I use internally.
I use like the black formatter on it before I write out the auto-generated code.
Oh, that's cool.
So your generated code is pep8 and all the goodness.
Yeah.
So if you look at it, you're like, oh, that's quite nice.
That's clever, actually.
Yeah, that's really clever.
Yeah, it's cool.
Black.
Yeah.
I feel like other frameworks, like for example, the cookie cutter stuff, you know, you're just
generating code files like crazy.
I feel like you could apply that same technique as after we inject all the user entered values,
let's just do a quick format on them and then drop them.
Yeah.
It makes sense because otherwise you'll run your linters on your project and they'll fail because
your migrations aren't correctly formatted.
Yeah.
No, that's cool.
A quick question from Teddy out in the live stream.
It says, I don't use ORMs much in my day-to-day.
What are good use cases besides web apps for them and where does Piccolo perform better?
So two questions.
So I think that data science is obviously a big bit.
So another reason for building Piccolo is data science is so much on the ascendancy in the
Python world and people are just, you know, still dealing with databases on a day-to-day
basis.
So you can use it in a script if you like, but there's maybe a couple of examples in the docs
where, you know, you might be scraping some data from a website and then you just need
to stick into Postgres.
So that would be another good example for using Piccolo.
Yeah.
That is a good example.
And then where it performs better.
It's really where you need the async or you might still want a web app component, even if
it's just like a data science script, you still might want an admin screen to view that data.
Yeah.
I think that performs better.
You could maybe break into two pieces.
Where does Piccolo perform better?
And I think the async stuff is really important there, like you say.
And then where does an ORM perform better?
You know, when you talk about performance and getting stuff done, like sometimes it's how
fast is the code run, but sometimes it's how fast do I get the final thing built.
Right.
And I think ORMs, even if they're not always the most efficient way, sometimes they're really
efficient, but not always.
But, you know, they could help you safely get to the other side, especially if you don't
know SQL super well.
Yeah.
They hold your hand a little bit.
Yeah.
I created this while working at a design agency and speed's really important at a design
agency, not really execution speed in terms of SQL queries, but in terms of scaffolding
an app and being productive.
So there's, so Piccolo has something called Piccolo asking you, and then to use that command,
it'll basically scaffold you a web app.
So it supports FastAPI, Starlet, Blacksheep.
Yeah, that's really cool.
So yeah, it's, you just kind of create your starter code and your starter structure from that,
right?
Yeah.
So a bit like with Django where you, you know, you create a project on the command line,
but with Piccolo, you get an option of different ASCII frameworks.
And over time, I'd like to add, you know, way more because there's, there's, there's
many more exciting ASCII frameworks like Quart, Sanic, Django itself is actually an ASCII
app, so it could support Django.
Yeah.
Django has come along there.
So if people were out there listening and their favorite framework wasn't listed in one
of those three or wasn't one of those three, PRs are accepted, I guess, and they could
integrate, you know, their, their favorite Sanic or whatever they're after.
Yeah, definitely.
Like any feedback's really appreciated.
So like the community has definitely helped me a lot with Piccolo, just, just as much as
trying it and giving feedback or, you know, obviously pull requests are also like really
valued.
Even if you just want to raise an issue to say, well, how did you do this?
Like, you know, that's still welcome.
Yeah.
Awesome.
Well, well, maybe that's a good place to talk about where things are going in the future
and kind of wrap up our conversation a bit.
Yeah.
So I feel like I'll never be bored with Piccolo because Postgres is continuously developing
and adding new features.
And I almost feel like Postgres is almost like an operating system in a way.
Like the amount it does is kind of insane.
So it even has like a PubSub built in.
You can do that.
Listen, notify.
Oh, wow.
I'd like to do post GIS support.
Timescale DBs are really up and coming extension as well for Postgres for time series data.
But then a lot of the stuff I'm excited about is like on the admin side.
So as I mentioned before, Piccolo admin effectively turns Pydantic models into UI.
So the next thing I want to add is you can basically give it arbitrary Pydantic models and it will
render them as forms in the admin.
So if you have, for example, you want to send an email, you'll just create send email model,
give it to Piccolo admin and it'll generate a form.
That's stuff I'm really excited about as well, just to increase the utility of Piccolo admin.
So a backend developer could build a functional app for a business without actually writing any code.
That's kind of the dream is to build like a really, really great admin.
Yeah.
These self-serve, like once you create the app and hand it off,
how far can people go before they have to hire your design agency again or something like that?
The more that they can just run with it, the better, I suspect.
Yeah.
It's such a huge benefit from Django, like having that admin.
So I just want to kind of see what I can do with the latest technologies to build a really great one.
Yeah.
What's the story with Django and Piccolo?
Is there a reasonable way to click them together or is it really not so much so far?
I think you can.
I haven't really tried it much, but it's very configurable, Piccolo.
So, you know, and none of the names, they deliberately don't clash with Django.
So Django has a settings.py, Piccolo has a piccolo underscore conf.py.
And then Django has a migrations folder, but Piccolo has piccolo underscore migrations.
So there's no clash there.
So in theory, they would work together.
There's no like compatibility layer between the admins.
So you'd have like two separate admins or, but I'd like to add support for Django as it is an ASGII app
and it's the originator of the ASGII standard.
And I still think Django is one of the great kind of masterpieces in the framework world.
It's lasted so long and it's still such a rock solid choice.
I would like to see what I can do there.
Yeah.
The closer those could be, I think, right?
Like this having the genesis and so many similar ideas to Django,
it seems like they should be somehow working together, which is great.
Yeah, that'd be cool.
All right.
Well, Dan, I think that might be about it for time that we got.
Let me ask you the final two questions that I always ask.
If you're going to write some code, you can work on Piccolo.
What editor to use?
I think I caught a hint of it earlier, but go ahead.
Yeah.
VS Code all the way.
I was a huge Sublime Text and TextMate user.
And I was like, I'll try out this VS Code, see what all the hype's about.
And after five minutes, I was never going back.
I just think it's such a great gift to the world from Microsoft.
It just gets better and better as well.
Yeah.
Love VS Code.
Right on.
And then notable PyPI package you want to give a shout out to?
So I'm going to cheat and pick two.
So Pydantic, because I think it's such a nice serialization library.
And I think it could almost be in the standard library.
It feels so Pyphonic and natural.
And then Starlet, because I think it's just a beautiful foundation for the ASCII world.
And I'd really encourage people to look at the code to see the power of ASCII, how it
is this like turtles all the way down.
Everything's ASCII.
It is quite interesting.
So those would be my two shout outs.
Yeah.
Very cool.
You know, FastAPI is so popular now, but FastAPI is kind of like an opinionated view on top of
Starlette to a large degree.
Yeah.
FastAPI is great as well.
Yeah.
Well, I mean, it takes the two things you said, Pydantic and Starlet, and puts them together
in like a nice way, which I think is pretty neat.
Yeah.
It's got great taste.
Yeah, for sure.
I'd just like to say one thing really quick, and just thanks to everyone who's contributed
to Piccolo, because there's been people who've been contributing for several years by this
point and have put a lot of work in.
So yeah, just a shout out to anyone in the Piccolo community.
Yeah.
And, you know, Final Call to Action, people are interested in using this.
It's good to go.
It's ready for production, web apps, and all that kind of stuff.
I didn't really want to promote it before it was ready.
And I use it in production.
I have done for years.
And I am quite conservative about pushing stuff out there, unless I think it's, you know, solid.
It's got 100 unit tests.
You know, it's solid.
I'm not saying there's not some edge case I haven't discovered yet in some version of,
you know, Postgres or something.
But I use it in production every single day.
Well, congrats on building a really cool Pythonic ORM.
I really like the way that you put things together.
And yeah, it looks great.
It's got a lot of nice modern Python features.
And people should definitely check it out.
Cool.
Yeah.
Thanks a lot, Michael.
Yeah.
You bet.
See you later.
Yes.
Bye.
This has been another episode of Talk Python to Me.
Our guest on this episode was Daniel Townsend.
And it's been brought to you by Linode and us over at Talk Python Training.
And the transcripts are brought to you by Assembly AI.
Simplify your infrastructure and cut your cloud bills in half with Linode's Linux virtual machines.
Develop, deploy, and scale your modern applications faster and easier.
Visit talkpython.fm/Linode.
And click the create free account button to get started.
Transcripts for this and all of our episodes are brought to you by Assembly AI.
Do you need a great automatic speech-to-text API?
Get human-level accuracy in just a few lines of code.
Visit talkpython.fm/assembly AI.
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.
Thank you.
Bye.
Thank you.