This podcast episode that you're listening to right now was delivered to you in part by MongoDB and Python, powering our web apps and production processes.
But if you're using PyMongo, the native driver from MongoDB, to talk to the server, then you might be doing it wrong.
Basing your app on a foundation of exchanging raw dictionaries is a castle built on sand.
And by the way, see the joke at the end of the show about that.
You should be using an ODM, an object document mapper.
This time we're talking about Beanie, which is one of the exciting new MongoDB ODMs, which is based on Pydantic and is async native.
Join me as I discuss this project with its creator, Roman Wright.
This is Talk Python to Me, episode 349, recorded November 18th, 2021.
Welcome to Talk Python to Me, a weekly podcast on Python.
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 Sentry and us over at Talk Python Training.
Please check out what we're both offering during our segments.
It really helps support the show.
Hey folks, it's great to have you listening as always.
A quick bit of news before we talk with Roman.
I've been looking for a way to explore ideas teaching Python, get some feedback, and then bring what I've learned back to the course content.
So I'm kicking off a new initiative over on YouTube.
It's called Python Shorts.
And the goal is to teach you one thing, both interesting and useful, as well as actionable, about Python.
I have three videos out so far.
The first, parsing data with Pydantic.
The second, counting the number of times an item appears in a list with counter.
And third, do you even need loops in Python?
I also published a fourth video about using the Stream Deck, that little button device that a lot of gamers use, to make Python developers more productive.
Check out all four of those videos on my personal YouTube channel, not the Talk Python one.
Links in the show notes.
Now, let's talk Beanie.
Roman, welcome to Talk Python to Me.
Hey, I'm happy to be here.
Yeah, it's great to have you here.
Oh, we get to talk about one of my favorite topics, MongoDB.
I'm so excited.
Yeah, my favorite topic, obviously too.
Oh, yeah, you definitely put a lot of time into it.
We're going to talk about your ODM.
People often hear about ORMs, Object Relational Mappers, but traditionally, MongoDB and other document databases and NoSQL databases haven't modeled things through relationships.
It's more through documents.
So the D instead of R, ODM.
Beanie, which is going to be super fun.
It brings together so many cool topics and even relationships.
So maybe if you really wanted, I guess you could put the R back in there as of recently.
Anyway, super fun topic on deck for us.
Before we get to that, let's just start with your story.
How did you get into programming in Python?
I started when I was a student, more than 10 years ago now.
But I started not with Python.
I started, it was 2008, I think, 2007 probably.
I started with Flash, and nobody knew that Flash will die soon.
Flash was such a big thing when it was new.
I remember people were just completely getting amazing consulting jobs and building websites with Flash.
I'm like, what is this?
I'm not sure I want to learn this, but do I have to learn this?
I hope not.
But yeah, anyway, yeah.
It did kind of get killed by HTML5.
And, you know, I think, honestly, maybe it got killed by Steve Jobs, really.
At least earlier than it would have been.
Definitely.
Yeah.
So, but that time I didn't know that.
And ActionScript, two and three.
Right on.
And then I decided to move to backend problem.
So Flash is a client technology, if anybody don't know.
There's no.
And then I moved to, I just wanted to build websites and wanted to move to backend.
And I chose Django.
Mm-hmm.
Not Python, but Django.
Because Django was super fancy at that time.
And now also, but that time, yes.
And I was a Django developer, not Python developer, but Django developer for a year, probably.
Because I knew a few tricks and tips about Django tools, models, views, and et cetera, templating.
And then somehow I learned Python also.
And now I'm always a good Python developer.
Yeah, fantastic.
I think I just realized as you were speaking, we might have to tell people what Flash is.
I feel like it's just one of these, you know, iconic things out of the tech industry.
But it might be like talking about AltaVista.
At some point, the kids, they won't know what Flash is.
They won't know there was this battle about getting this thing on people's computer.
And it was always like viruses are being found in them, but it could always do this magical stuff.
How interesting.
Yeah, yeah.
How old we are.
Exactly.
The other thing that is interesting is you said you chose Django.
You didn't choose Python.
Yeah.
Yeah, I think that that's, you know, now people are often choosing Python because of the data science and computational story.
But before 2012, there was not this massive influx of data science people.
And I think that the big influx was people becoming Django developers and like, well, I guess I'll learn Python.
Kind of like people becoming Ruby on Rails developers.
Like, I don't know Ruby, but I want to do Ruby on Rails.
So I guess I got to learn Ruby.
Django is kind of our version of that, right?
Right.
Yeah.
Then I think Flask appeared after a few years and everyone started to do microservices with Flask.
And after that, the game started.
That's right.
Oh, my goodness.
Out in the live stream, we're getting some high fives from Pradvan on Django.
And Jeff says, he says Django the right way.
Fantastic.
So awesome.
Yeah.
And I think Django has been massively important for Python.
And very, very cool.
Yeah.
How about now?
What are you doing day to day?
So I'm principal Python developer now.
And this is interesting.
Actually, I changed my job three months ago.
And I didn't look for a new job.
But one day, Moot, my current manager, just texted me into Twitter that, hey, I saw your library, Bini.
And it's interesting.
And we need something like this in our project.
And probably you would like to participate in and integrate Bini into our project and develop Bini at the work time.
Oh, how cool.
And I decided, yeah.
Yeah.
That's amazing.
I mean, on one hand, it would just be cool to have a fun job doing cool MongoDB stuff, right?
On the other, it's like, oh, my gosh, I get to use my library and build up my library in a real production environment.
Like, that's awesome, right?
Yeah, yeah.
And so I decided the same day, I think.
Let me think about it.
Yes, okay.
We'll figure out how long it takes me to quit my current job.
Awesome.
Well, congratulations.
That's really cool.
Yeah, thank you.
That only means good things for Bini, right?
It only means more time and energy on it, I would suspect.
Yeah, yeah.
All this, the current release, the huge release is possible only because I can work a little bit time on work time, not only on weekend.
Yeah, absolutely.
It's easy to justify, like, the library needs this feature to work right for us.
So let me add that feature to the library as just part of the sprint or whatever, right?
And also probably it's even more important that when I can work on production with Bini, I can also see what does it mean.
Yeah.
Which feature and which improvement.
It's so true because there's just these little edge cases.
They don't show up under even a complicated little example you build for yourself.
You know, you've got to put it into production and live with it.
You know, some of that stuff might be migrations, right?
Like, I never need these migrations.
Oh, wait.
We have the zero downtime promise.
We kind of need migrations now or something, right?
Right.
Totally correct.
Yeah, yeah.
Cool.
Well, that's great to hear.
Congratulations.
Now, I wanted to start our chat off not talking about Bini precisely, but like a little lower in the tech stack here.
Let's just talk about MongoDB for a little bit.
I'm a huge fan of MongoDB, as I'm sure many of the listeners know out there.
I've been running Talk Python, Talk Python training those things on top of MongoDB for quite some time.
In the very, very early days, some of that was SQLAlchemy stuff, but then I switched over to Mongo and whatnot.
So I'm a huge fan of it.
I definitely think there's a lot of value.
And there's a lot of these architectures where people talk about, oh, we have a Redis middle tier cache because we got to have our website fast.
You know what?
We get 10 millisecond response time and there's no cache.
It's just talking to the database because, you know, everything is structured the right way.
I think it's, anyway, I'm kind of going on too much.
But what I wanted to start with was I want to hear your thoughts on just sort of why build on top of Mongo.
You know, so many people in the Python space build on Postgres, which is fine.
It's a good database and all is just a completely different modeling story.
So why are you interested in Mongo?
First of all, Mongo is super flexible database by design.
And I really like to do prototypes.
So when I just come up with new idea of new project, home project and et cetera, it's quite simple to work with MongoDB instead of Postgres when you have to change painfully schema of your data.
But with Mongo, you can just do what you want to.
That's been my experience as well that I remember almost every release of my code would involve some migration on the SQLAlchemy version.
I think I've done one, what you would consider a migration in like five years on MongoDB.
Whereas everything else is like, I'm going to add this equivalent of a table or I'm going to add a field to this document.
But it just goes in and it just, it adapts.
It's fantastic.
It's like plastic instead of something brittle.
Even in the indexes.
Yeah.
That's why it's great for me, I think.
Yeah, it's really easy for prototyping, right?
You just, instead of trying to keep the database in sync or whatever, you just work on your models and magic happens.
Yeah.
Yeah, you're right.
And then for sure for production, you have to understand your profile and your needs.
And then you can move to Postgres or you can move to something like timestamp database, ClickHouse, for example.
Yeah.
But in most cases, Mongo is enough.
And for some cases, Mongo is the best choice because of flexibility and because of many cool stuff like indexes and such.
Yeah, absolutely.
I think indexes.
I don't know.
This is maybe getting ahead of ourselves, but I think indexes are just so underappreciated in databases.
I mean, I know a lot of people out there make sure their queries have the right indexes and stuff in it.
But there's also so many websites I visit that take three seconds to load a page.
I'm like, there's no way there's an index on this query.
There's just no way.
I don't know what it's doing, but somebody has just not even thought about it.
And if it was a weird little, oh, here's like the reporting page.
Fine.
But it's like the homepage or something.
You know what I mean?
Like, how are they not making this faster even in like a Postgres story, right?
Like, I feel like there's one thing to have a database that does something.
There's another to like tune it to do the right thing, regardless of whether it's relational or NoSQL.
Yeah.
Totally correct.
Yeah.
You have a lot of experience with databases.
I mean, you must have that feeling as well.
You go to some website, you're like, what is it doing?
Why is this thing spinning?
What could it possibly be doing here?
Yeah, probably somebody's going, you know, to get data.
Exactly.
I mean, you're thinking through the ideas.
Okay.
Is it just not half an index?
Or is it an N plus one problem with some ORM?
Or where, why am I waiting here?
What mistake have they made?
Fantastic.
All right.
So the next thing I want to sort of touch on is this tweet from Scott Stoltzman sent this out yesterday.
I don't think he knew that we were coming up with this conversation, actually.
So there was this programming humor joke.
It says, it has like two, I guess one of them is like a kitchen.
The other is an office, but it doesn't really matter.
Like the kitchen is super organized and it says MySQL.
Everything's little buckets and put away nice.
And then there's a desk area that's just, it looks like a hoarder house or like a, you know, earthquake hit and destroyed this area.
And it says MongoDB.
And it's actually true.
It is.
It can be true.
And the reason I bring this up is Scott said, you know, I know a guy who made this course that saved me from this chaos with Mongo Engine.
Because it can happen with, it can happen in about 15 seconds without a strong plan.
And so out of the box, the way MongoDB, the dev folks there suggest that you, or at least provide, let's put it that way.
The tools they provide for you to work with MongoDB are dictionaries.
Like you can give us dictionaries and put them somewhere and then you can get dictionaries back.
And Python dictionaries are just whatever, right?
There's zero structure.
There's zero discoverability.
There's zero type safety, right?
Sometimes it's a string that looks like a number.
Other times it's a number.
Good luck.
Those don't match in a query.
You know, like it's horrible.
And so for me, I feel like what you need to do when you're working with databases that have less structure in the thing itself, like this is MySQL, say Postgres.
Like Postgres says the table looks like this.
This column is that size of a string.
This is a number and that's it.
You know, like the structure is in the database where in these document databases, the structure is in the code.
And so you should have some kind of code that helps you not end up in a situation like this, right?
Yeah.
Honestly, Postgres also came up with JSONB fields now.
Yeah.
So they're kind of, maybe they fall into that bottom bucket like in a small little area.
Yeah.
This portion of Talk Python to me is brought to you by Sentry.
How would you like to remove a little stress from your life?
Do you worry that users may be encountering errors, slowdowns, or crashes with your app right now?
Would you even know it until they sent you that support email?
How much better would it be to have the error or performance details immediately sent to you,
including the call stack and values of local variables and the active user recorded in the report?
With Sentry, this is not only possible, it's simple.
In fact, we use Sentry on all the Talk Python web properties.
We've actually fixed a bug triggered by a user and had the upgrade ready to roll out as we got the support email.
That was a great email to write back.
Hey, we already saw your error and have already rolled out the fix.
Imagine their surprise.
Surprise and delight your users.
Create your Sentry account at talkpython.fm/sentry.
It's also one of the things.
Why schemas about structure?
So the thing I think saves you from, you know, Scott mentioned MongoVidgin, which is pretty good.
But I think the thing that saves you from this are these ODMs.
Like you have a lot more structure in your classes and your Python layer, right?
Yeah, yeah, correct.
And also, so, yeah.
Beanie is ODM based on Pydantic.
Pydantic is Python library, the data and stuff.
Yeah, let me read the little introduction bit here.
Because I think there's so much in this first sentence.
So Beanie is an asynchronous Python object document mapper, ODM, for MongoDB.
So ODM we talked about, Mongo we talked about, asynchronous, and also I didn't finish the sentence,
based on motor, which comes from MongoDB, and Pydantic.
So it's also asynchronous, right?
Which is pretty awesome, as in async and await.
And so often, the models that we build for the databases are their own special thing.
And then you've got to build maybe an API, and you might use Pydantic or something like that.
But Pydantic's been really coming on strong as a super cool way to build object trees and object graphs and stuff.
And so Pydantic is a perfect thing to say, well, let's just use that.
Everyone already knows how to use that.
And things like APIs can already exchange those on the wire.
Yeah, yeah.
And that's why I chose this.
It's just such a neat combination of bringing the async and await stuff together, along with Pydantic,
and saying, let's see if we can use those ideas for the basically for the ODM.
So there's other ones.
You know, Scott mentioned Mongo Engine.
That's actually what I'm using right now for my MongoDB stuff in Python.
It's Mongo Engine's pretty good.
I feel like it's kind of wherever it's going to be.
There's not a ton of excitement in terms of like new features and pushing stuff forward.
For example, there's, to my knowledge, there's no async and await stuff happening in there.
There's, I think it's synchronous.
Possibly there's something that's happened that's changed up.
But the last time I looked, it was synchronous still.
Weeks ago, it was synchronous, yeah.
Yeah, okay.
That's much more recent than I've looked.
And what are some of the other ones?
I'm trying to think of some of the other MongoDB ODMs out there.
I know in another language is probably like Mongoose in JavaScript.
Yeah.
Active record stuff for Ruby and things like that.
Yeah, yeah.
So a lot of these systems were based on the Django ORM model.
So for example, like Mongo Engine is basically Django ORM, but adapted for documents in Mongo, right?
Like the terminology and everything is quite similar.
Being based on Pydantic, yours is a little bit different, right?
I feel like there's a lot of interesting things you've, choices you've made.
One, to be based on Pydantic and how that works.
But two, we'll get into the API and stuff.
But when you look at the API, I feel like you've chosen to be very near MongoDB's native query syntax or query language.
So for example, instead of doing a select, you would do like a find or find one or the updates.
And like a set operator is one of the things you can do, like a set a value on there.
Was that conscious?
Did it say, like, I'm going to try to be really close to MongoDB or what was the thinking there?
When I started to work on Beanie, it was not Beanie.
It was just a side project because, so yeah.
On the very beginning, I started to play with FastAPI.
It was a very modern web framework at that time.
Now it's still modern and great web framework.
But at that time, it was quite new around a few years ago.
And this is a synchronous also and uses Pydantic under the hood.
And there were no ODM, no sync ODM for Python and MongoDB.
I had the same experience.
Like, I wanted to use MongoDB with some FastAPI stuff.
I'm like, there's not a great library that I can pick here.
So I guess I'll just use SQLAlchemy or something, right?
And what I did, I just started with Pydantic.
I've got Motor.
It is PyMongo.
So yeah, this is an engine over PyMongo.
And it is a synchronous engine.
Yeah, yeah.
Let's talk about Motor a little bit because I suspect that most people who work with MongoDB work with PyMongo.
Right?
When I opened the conversation, I said, the tools they give you are just, here's a dictionary to put in.
And then I get dictionaries back out.
I was exactly thinking of the PyMongo library.
Right?
And so what's this Motor thing?
This is also from MongoDB.
This is also from MongoDB, right?
And it also reflects each method and function from PyMongo.
But it's converted, let's say, into a synchronous method and function.
So it uses the same, mostly the same syntax as MongoDB itself and as PyMongo also.
Okay.
Yeah.
So it's a lot like PyMongo.
It says Motor supports nearly every method PyMongo does, but Motor methods that do network I.O.
are coroutines.
So async and await type of things, right?
Yeah.
And what I did, I just combined together Pydantic and Motor without any query builder stuff and other ODM fancy stuff.
Just querying, using dictionaries, the same dictionaries as Motor does.
And PyMongo for models and nothing more.
Small library.
It wasn't Bini at that time.
It was an internal project just to play with FastAPI.
And then after a few months of working, I decided probably I can make it open source.
I came up with the name Bini.
And mostly that's why I'm following MongoDB naming, not select, not join, et cetera.
Just find the many, find one, update, et cetera.
Because I'm using, I started to use directly Motor functions inside of Pydantic stuff.
And only after that, I just started to update Bini stuff with more fancy stuff.
Yeah.
So it was a pretty close match to like, how do I take Motor and just make it send and receive Pydantic instead of send and receiving straight dictionaries, right?
Yeah.
So the first project was just a parser from Motor to Pydantic and back.
Cool.
Well, I think it's really neat.
And I, you know, however you got there, I think it's really nice that you have the API that matches that.
Because then I can go to the MongoDB documentation and, or I can find some other example that somebody has on, well, here's how you do it with PyMongo.
And you're like, well, that looks really close to the same thing over here.
So we can talk about that.
Now, I want to dig into some of the other features and stuff there.
For example, data and schema migrations and support and whatnot.
That's pretty cool.
Yeah.
But let's talk about modeling data here.
That's, you know, the first letter in ODM, the object bit.
So what does it look like?
I know, well, maybe not everyone listening knows what it looks like to model something with Pydantic.
So maybe we could give us a Pydantic example, and then we could talk about how to turn that into something that can be stored in MongoDB.
Yeah.
Pydantic is base model.
The main class of Pydantic is base model.
And you inherit everything from base model.
And it looks like data classes of Python.
But it also supports validation and parsing, which data classes does.
Yeah.
And the conversions and stuff, it's really cool.
That's one of the things I think Pydantic is so good about.
Yeah.
If you look at their example, the way you define these classes is you have a class and you just as a class level, not instance level, you'd say name, colon, or variable, colon, type, variable, colon, type.
So in this example, you've got a category, say name, colon, str, description, colon, str.
And this just means this thing has two fields.
They're both strings.
But the Pydantic example on their website has got some kind of model where it's got multiple fields.
And one of them is a list of things that are supposed to be numbers.
And if you pass it a data and that list happens to have a list of strings that can be parsed to numbers, it'll just convert it straight to numbers as part of loading it.
It's really, really nice, right?
Yeah.
This is a great feature.
Yeah.
This exchange across, especially across either files or API boundaries, right?
Somebody writes some code and they send you some data.
Well, if they didn't really use the native data type, but it could be turned into it, then, you know, that's really nice.
And you also can add your own parser, like your own validation step.
And then it will convert this here rule, like from string to number, to date.
So, yeah.
And the Bini uses the same schema.
The same.
So, document is a main class of Bini.
And it's inherited from Pydantic base model and inherits all the methods, all the aspects of base model of Pydantic.
So, it can use the same validation stuff, the same parsing stuff, and et cetera.
Right.
So, whatever people know and think about Pydantic, that's what the modeling looks like here.
I guess the one difference is when you talk about, when you model the top-level documents that are going to be stored and queried in MongoDB, you don't derive from base model, right?
Drive from something else?
Yeah.
From document.
Right.
And document, it comes from Bini.
But that document itself is derived from base model, ultimately, right?
So, even though you got to do this little more specialized class, it's still a Pydantic model in its behavior, right?
Yeah.
Yeah, correct.
That's why you can use it as a response model, for example, in FastAPI, if you're familiar with.
Yes.
Because it uses identity base models and submodels of base models.
Yeah, if people haven't seen that, let me, here's an example.
One of the things you can do with FastAPI that's super, super neat is you can go and say, as part of returning the values out of the API, one of the things you can do in the decorator is you can just say,
the response model is some Pydantic class.
And if you just add that one line and that class happens to be a Pydantic model, you get all sorts of live documentation and API definition stuff.
And there's even code that will consume that.
So, I built this little weather thing in FastAPI for one of my classes over at weather.talkpython.fm.
And you just go to slash docs.
It pulls up, oh, here's all the APIs you can call.
And here's like the return value with, you know, exactly what the schema is.
And all of that's just from that one line of code.
So, what you're saying is you could do that with these database models.
You could just say, I'm going to return this directly from my API back to you.
And if you just say response model equals your data entity model, you get all this for free, right?
Yeah, it's also invalidated based on this model.
If you're, for example, using some external source of data and they changed schema and you received it and responded back.
And if the format is not correct, it just will erase an error and you can gracefully handle this.
Yeah.
Nice.
Yeah, the error that it returns is also meaningful.
Like, the third entry in this list cannot be parsed to an integer rather than, you know, 400 invalid data.
Good luck.
Okay, so we've modeled these objects in here.
And I guess one more thing to throw in while we're talking about modeling.
In your example, right at the bottom of the GitHub page, you have a category which has the name and description.
And you have a product that has its own name and description.
And then a price.
And the product has a category.
And here you just say colon category, right?
And that would make, I guess, would make this, in this case, an embedded document inside of the product document, right?
Yeah, totally correct.
It will just create embedded documents.
Yeah, so the way that you model.
This is why Pydantic's a really good match because Pydantic allows you to compose these Pydantic models in this, like, hierarchy.
You can have, like, lists of other Pydantic models within a Pydantic model, which is exactly the same modeling you get with document databases like Mongo, right?
Yeah, yeah.
So you don't have to do anything.
It's like, it just, it keeps modeling the things I wanted to model.
Cool.
So, in other database systems, like SQLAlchemy or something, you would be able to say, this field is nullable.
Or not, or it's required, or something like that.
So how do you say that in this model?
If it's nullable, yeah, I can just use Python type in optional.
It's not a class, but optional, optional.
Yeah.
Mark it as optional.
Like optional bracket str versus just str, right?
Yeah.
And I guess for a default value, you just set it equal to its default value, right?
You're right.
But if it's optional, so it's explicitly marked here, but Pydantic allows you to use optional without default value, and default would be none in that case.
Because Pydantic is...
I'm thinking, like, false or none or something like that.
In this case, you probably wouldn't make it optional.
You would just give it a default value, right?
Yeah.
Correct.
Now, one of the things that I can do in, say, Mongo Engine, is I can say the default is a function.
Because maybe this is incredibly common in my world, is I want to know when something was created.
You know, when did this user create an account?
When was this purchase made?
When did this person watch this video or whatever?
And so almost all of my models have some kind of created date type of thing.
And the default value is datetime.datetime.now without parentheses.
You know, like, I want you to call the now function when you do an insert.
How would I model that in Beanie?
Again, you can use Pydantic stuff here.
So I really like Pydantic.
They did half a fork.
You can use field class, equal field.
And inside of that, you can use default factory parameter, where you can just provide function you want to.
Okay.
Oh, that's right.
So you can, instead of setting it, like, you could say it's a string, but its value is, what is it?
You say field factory or something?
You said it to be one of these things that Pydantic knows about.
Didn't get you.
Sorry.
Is that thing that I'm setting it to equal to?
Does that come out of Pydantic?
Like, that's not a Beanie thing?
This is Pydantic stuff.
Yeah.
Default factory stuff.
But there is interesting Beanie feature about this.
I can show you in another code example here.
For example, if you want to use not create at, field but updated at.
So, and each time when you update, you want to update time.
And for that case, default factory will not work.
Because default factory...
It's already got a value.
Yeah.
It's only on create.
Yeah.
And Beanie allows you to use event-based actions for this.
And you can just create a method of the class.
And there, mark it like run before event.
Before event decorator.
Yeah.
Before event.
And inside of this, you can just write your logic.
Self updated at field equals current time.
And it will work.
For events, you will choose like insert, replace, and anything else.
Yeah.
This is one of the new features.
We're going to talk about some of the new things.
But one of them are these event actions.
So, you can say before the insert event or before the save event happens or after the
thing's been replaced or any of those types of things, you can sort of put a decorator and
say run this function when that happens on this type or in this collection effectively.
Right?
Yeah.
Very, very cool.
All right.
We're going to dive back into that because that's good.
And so, that would be actually a pretty good way, wouldn't it?
Just do an event on insert.
And when on insert happens, set my created date to be datetime.now.
Yeah.
Okay.
Good.
And then, I guess, the other part that's interesting now is doing queries and inserts on this.
So, you would create your objects just exactly the same as you would Pydantic, right?
Just class name, key value, key value, key value.
Like that?
Yeah.
Or even parse object like category dot parse object and dictionary with values.
And yeah.
If you want to parse more than one object.
Right.
If it's coming.
Okay.
Or if it's coming out of an API or something like that, right?
And then you would say, here's where it gets interesting.
You say await object dot insert or await class dot find one, right?
Or await set some value there.
Yeah.
Because this is, I think, await library.
So, that's why you have to use await here based on the asynchronous nature of the library.
Sure.
And that's the whole point, right?
Is that it's built around that.
I think, I mean, there's ways in which you could use it in asynchronous situations, right?
You could always create your own event loop and just run the function and just block right that way.
Or use something like unsync, which maybe we'll touch on a little bit later.
But yeah.
So, but if you're doing something like Flask or FastAPI, where the functions themselves,
the thing being called by the framework is already handling it, it's basically no work, right?
You just make an async method and then you just await things and you get this,
you unlock this scalability right there.
Yeah.
I think modern Python world is pretty everywhere asynchronous already.
I don't know if I think framework development now, like most of them are asynchronous.
Yeah, exactly.
I think with Flask, there's limited support for async.
And then if you want full async, you have to use court for the moment, but maybe stuff's happening.
I know Django is working on an async story as well.
Yeah.
I think the big holdup for like full on async and Django has actually been the Django ORM.
So this would fix that.
Although, does it make any sense to use Beanie or another ODM or something like that out with Django?
Or I mean, it's so tied into its ORM itself, right?
I think for Django, it's a little bit tricky.
Yeah.
Probably things changed, but Django works with relative databases, yeah, with SQL databases,
and also the Django model stuff.
Yeah.
It's possible.
Definitely it's possible.
It's Python.
Everything is possible.
Yeah.
It's just, you know, if you're fighting against the system, then you maybe should just choose a different framework rather than try to fight the way that it works, right?
Like if you're going to choose something like Django that gives you a lot of opinionated workflow, but a lot of benefit if you stay in that workflow, then just, I'd say, follow that.
But the other frameworks are pretty wide open, right?
You could easily use this with Flask.
You could use this with pretty much anything.
It's better if it supports async, right?
There's not a synchronous version, is there?
Yeah, there is no synchronous version of this, unless it uses a motor insight, as we said.
I'm thinking about by Mongo support without motor.
In that case, it would be just synchronous.
I would think that would be great, honestly.
I'm very excited that this is async first.
I think that's really good.
But let me give you an example.
So for my website, I would love to be able to make all the view methods be async, right?
That would give it a little bit more scalability.
It's pretty quick, like I said, but it would still be way more scalable if it could do more while it's waiting on the server.
But at the same time, I have all these little scripts that I run.
And here's how I want to go and just show me all of the podcast episodes who is sponsoring them to make sure that if I had to reorder things, I don't mess up some sponsorship detail.
Or show me all the people who have signed up for this class this month and whatnot.
Those little scripts, right?
It would be nice if I could just say these little things are going to be synchronous because it's the easier programming model.
I don't have to do that.
What do you think of that?
I do agree totally.
But I have limited time.
That's why.
Yes, of course you do.
And I guess that pull requests are accepted or contributions are accepted if there's meaningful good work, right?
Yeah, sure.
Sure.
Yeah, cool.
I'm not suggesting that it's like a super shortcoming, right?
It's not that hard to create an async method and just call async io.run on it.
But having this ability to say this situation is really a synchronous one, don't need to go through the hoops to make that happen.
Even I had this situation in the past, you know, and I had to create a loop inside of synchronous function.
Yeah, exactly.
Exactly.
I think that's worth touching on a little bit because people say that async and await is like a virus or something.
Like once one part of your code base gets async, like it sort of expands upward so that anything that might touch that function itself has to become async and then its colors have to become async and so on.
In the most naive, straightforward way, that's totally true.
But it's not true if you don't want it to be, right?
Like halfway through that function, that call stack, you could say on this part, I'm going to create an async io event loop and just run it and just block.
Right?
And anyone who calls that function doesn't have to know it's async io, right?
Yeah.
You can sort of stop that async propagation.
And that sounds like that's what you're talking about, like creating a loop and running it inside of a synchronous function.
Yeah.
But this looks really super ugly.
Yeah.
You even don't have any chance to await this.
You're creating a task.
Yeah.
It is a little bit weird if you haven't seen it.
I, okay.
So it's time for my requisites required.
Shout out to the unsync library, which I think is just so neat in the way that it simplifies async and await in Python.
We're talking about this just a little bit before I hit record, but it has basically two things that are frustrations that make this kind of stuff.
We're talking about a little bit harder.
Like one, wouldn't it be nice if you could just call an async function and it just runs?
Like I want to write this to the log, but let's do that asynchronously and just go, go right to the log.
I don't want to see from you again.
I want to hear from you again.
Go put it in the log.
I'm going to keep on working, right?
You can't do that with Python's async.
You've got to like put it in a loop and make sure the loop is running.
So this fire and forget model doesn't work.
And the other is you can't block.
You can't call dot result to make it block.
If it's not done, it's going to throw an exception, right?
So this unsync thing lets you put an at unsync decorator on an async method.
And then if you need to stop the async propagation, you just call dot result and it'll block and then give you the answer.
Builds done.
And there's all sorts of cool integration with like threads plus asyncio plus multiprocessing.
It's, I think this fixes a lot of those like little weird edge cases.
I think I will try this after this podcast and will create a page on documentation.
So if you need to try it out and see if it's a good idea.
It might not be a good fit, but I think it is actually.
I think it would be.
I will try.
So if it will not work, I will not.
So basically the way it works is it creates, when it starts up, it creates a background thread.
That its only job is to run an asyncio event loop.
And then when you do stuff, when you call functions on this, instead of running the main thread, it just runs on that background thread.
And when you block, it just waits for that background thread to finish its work and stuff like that.
So it's, that's sort of the trick around it, but super big fan of unsync.
I think it's, it does a lot of good for these situations that we're talking about where you're like, okay, almost all the time.
And definitely in production, I want it async, but every now and then I want it to just stop.
Talk Python to me is partially supported by our training courses.
We have a new course over at Talk Python.
HTMLX plus Flask.
Modern Python web apps hold the JavaScript.
HTMLX is one of the hottest properties in web development today.
And for good reason.
You might even remember all the stuff we talked about with Carson Gross back on episode 321.
HTMLX, along with the libraries and techniques we introduced in our new course, will have you writing the best Python web apps you've ever written.
Clean, fast, and interactive, all without that front-end overhead.
If you're a Python web developer that has wanted to build more dynamic, interactive apps, but don't want to or can't write a significant portion of your app in rich front-end JavaScript frameworks, you'll absolutely love HTMLX.
Check it out over at talkpython.fm/HTMX or just click the link in your podcast player show notes.
So let's talk about some of the other features.
Back to this example here, there's one thing I wanted to highlight that I think was really neat that I saw.
So you've got the standard.
I've created an object that goes in the database, and I call insert, and I await that.
That puts it in the database, right?
Yeah.
So I don't see a return value here.
Does that actually set the ID on this thing that's being inserted after that function call?
It updates.
Okay.
Perfect.
Yeah.
So .id is good to go after that, right?
Yeah.
Okay, cool.
And then the other one, you've got your await find one.
The filter syntax is the first thing I wanted to talk about, which I think is really nice.
Thank you.
So even though these things are, does it have to be one of these indexed ones, or can you do these queries of the style on any of these?
Could I do a category or a description equals something or a name equals something?
Yeah.
Yeah, sure.
You can do it with everything.
And you can do it even in the same line, in find one, price less than 10, comma, and name equals, I don't know, your name.
So it will work.
Nice.
So the way that you would say, I want to find the product, or all the products, that has a price less than 10, is you just say, in this case, a product is a class with a price field.
You just say product.price less than 10, right?
Just like you would in an if statement or a while loop or something like that.
Yeah.
This is really nice because the alternative is something like what you have in Mongo Engine, where what you would say is you would say price underscore underscore LLT equals 10.
Yeah.
Right?
You would say, like, separate the operators on the field with double underscores.
And so LT means less than, and then you equal the value you want it to be less than.
And that is entirely not natural.
It's not horrible.
You can get used to it, but it sure isn't the same as price less than 10.
Right?
Yeah.
That is really nice.
On the very beginning, when I told that I was Django developer, not Python developer, it was about this.
Because I knew how to do this stuff about Django, but it's not Python syntax, honestly.
It's Django syntax, which moved to.
Exactly.
So you can do these natural queries.
You got, like, less than, greater than, equal, not equal to, and so on.
Yeah, yeah.
For if it was none, I mean, thing is none is the most natural way, but you would just say equal, equal none.
Is that how you would test this?
Yeah.
You cannot hear.
So it's not supported to use is.
That's fine.
You can use only equal.
It's better than underscore, you know, price equals 10 or equals none, just as an assignment.
That's even weirder.
So that's cool.
Then the other thing that I thought was neat is so often in these ORMs, and it is worse in the Mongo story because each record that comes back represents more of the data, right?
In this case, you've got a product, and the product has a category, whereas those might be two separate tables in a relational database.
So the problem is I get one of these objects back from the database.
I make a small change to it, like I want to change the name, and then I call save, and it's going to completely write everything back to the database, right?
It's going to override everything, which can be a big problem.
There's a couple of solutions you have for that, and one is you have the in-place update operators, like set, and I'm guessing do you have like increment and decrement and add to set and those kinds of things?
Yeah, literally everything from which MongoDB supports.
Yeah.
Right.
So in this case, you can say product.set, and then product name is gold bar, right?
Rather than what was it before?
It was Tony's or something like that, right?
Yeah.
And that'll do a MongoDB dollar set operation, which is an atomic operation.
So somebody else could be updating, say, the category at the same time and sort of transactionally safe.
And so this way you're both way more efficient.
And it's also safer that you're not possibly overriding other changes.
Yeah.
And also in current version, it's possible to, so Beanie tracks all the changes of the object.
And when you call instead of set, you can call a safe changes.
And it will call set inside for all the changes which was happened with this object.
Yeah.
This was the other way that I was hinting at.
And it's super cool.
Where is this?
Save changes.
There we go.
On all of these documents, you can optionally have a class, an inner class called settings.
And then you can do things like use state management equals true.
And you don't have to figure out how to write those set operations or increment operations or whatever.
You can just make changes and call save changes and off it goes, right?
Yeah.
So it's quite simple.
Yeah.
This is really, really cool.
I like that about this, that it gives you that option to sort of use the most natural way of making very small changes to the data, right?
Because so often ORMs and ODMs are, give me the object back and make a change to it.
Put the whole thing back wherever it came from, you know?
Yeah.
I agree.
And also, if you don't want to fetch object at all and you want to set something to object, you can use update query here, right?
Yeah.
And you will not even fetch object into your application.
That's a really good point because so often with ORMs and ODMs, the set-based behaviors are super hard to do, right?
Like, let's suppose I've got 100,000 users.
I want to go and, you know, set some field to like a default value that didn't previously exist in the database.
Or I want to compute something that's a computed field that wasn't previously there.
So I've got to go to each one, make a change or something.
I mean, if it's always the same value, you still would need to go in the ORM stories, like, do a query, get the 100,000 records back, loop over them, set the little one value on the class and call save.
But what you're saying is you could just do, like, product, or in my case, users.update, value equals what you want it to, and just update all of them, right?
Update many, you might have to call or something like that.
Or you can even find something and then .update.
And it will update on only...
Oh, really?
So you could do like a find all, then a .update?
Yeah.
And it wouldn't actually pull them back from the database?
It will not fetch them, yeah.
Oh, my goodness.
Okay.
It's a bit magical.
That's very magical.
That's awesome, actually.
Then, again, the other things you have on here that are really just simple is, like, you can do a find and then just a to list on it.
You know, like, I don't want to loop over it or whatever.
Just give me the list back.
That's also nice.
Yeah.
Let's see.
How are we doing on time?
We're getting a little short on time.
Let me...
Let's talk through a little bit of some...
There's a really nice tutorial here that starts out with defining documents, setting up the code, which is pretty much just standard MongoDB, right?
Like, you have to create one of these clients, but what you're really creating is just a motor client.
So I'm guessing you can send as, like, complex of a MongoDB configuration as you need to, and it doesn't affect Beanie.
Yeah, and also now it's not documented yet because I'm lazy, but Anthony Shaw, you know him, I think?
Anthony Shaw makes common appearances here, yes.
Yeah.
He suggested me to add optional, so you can pass a connection string instead of all this stuff with database, just init, Beanie, and connection string, and it will work.
Nice.
But I have to add documentation about this because...
Sure, so in the documentation, you create a motor client, and then you pass the client over to Beanie init, or you just create the client first, right?
But if you just call init Beanie with the right connection string, it'll do that behind the scenes for you.
Yeah, thanks, Anthony.
That's really good.
So, but if you're working with, like, sharded clusters and replica sets and all the kind of stuff that is, like, on the outer edge of these use cases, that should be supported, right?
Yeah.
It's just under, behind the scenes, you don't have to know about it.
The other thing that's interesting is when you initialize it, you pass it all the document classes, like product or user or whatever, right, as a list?
Yeah, yeah, because you have to, so under the hood, document must know to which database it's picked because, so for some use cases, you can use different databases in the same application.
And in that case, you have to init Beanie for different databases with different set of models.
So, yeah, you have to pass models there, in that case.
Yeah, I do that in mine.
I have multiple databases, like logging and analytics and all that kind of stuff goes to one database that gets managed and backed up less frequently because it's, like, gigs and gigs of data.
But, you know, if you lost it, the only person who would care in the world is me, like, I lost my history of stuff, right?
As opposed to the thing the website needs to run or user accounts or whatever.
Like, those need to be backed up frequently and treated really specially.
So, I actually have those as two separate databases based on classes.
So, I guess what you're saying here is you can also call init Beanie on multiple times with different databases and different lists of documents.
Yeah.
And it will work.
Nice.
Yeah, that's really cool.
Can you give it, like, a star type of thing?
Like, everything in this folder, in this module or this sub package?
Not yet, unfortunately.
But it's a nice feature, I think.
Yeah.
Sounds like a feature request, yeah?
Okay, sounds like a feature request.
Yeah, so if you could give it something like, all my models live in this sub package of my project or in this folder.
Like, there you go.
That might be nice.
Because, you know, one of the things that happens to me often is I'll add, like, a view to some part of my site and I'll forget to register it somewhere.
Like, why is this a 404?
Oh, yeah, yeah, yeah.
I got to go and make sure the thing can see this file.
It will erase an error, an exception, errorless, and then you will call an endpoint.
Nice.
If you will try to touch any document without initialization, it will erase an error.
Yeah, cool.
So, let's see.
Let's talk indexes.
I started our conversation with my utter disbelief that there are websites that take five seconds to load.
And I'm like, I know they don't have more data than I have.
I just know they've done something wrong.
There's no way this has more data.
So, indexes are critical, right?
What is the index story?
How do you create them over here?
It's an interesting story about indexes, honestly.
Like, I published my first version of Beanie and one guy texted me that probably it's possible to add indexes there.
I don't see if it's supported or not.
And in a few days, I added them.
Yeah, that's right.
We covered Beanie when it first came out on Python Bytes.
I'm like, this is awesome, but where are the indexes?
A bit of a stickler for those.
That's awesome.
So, yeah, the way you do it is instead of saying when you define a class, say the type is, say, a str or an int, you would say it's an index of int.
And that just creates the index.
And it looks like, you know, in Mongo, you have all these parameters and control.
Is it a sparse index?
Is it a uniqueness constraint as well?
Is it ascending?
Is it descending?
And whatnot.
And so, you can pass additional information like that it's a text index or something like that, right?
Yeah.
Yeah, it supports all the parameters.
And uniqueness.
This isn't super important, right?
Like, your email on your user account had better be unique.
Otherwise, a reset password is going to get really weird.
You support multi-field indexes, which is something that's pretty common, like a composite index.
If I'm going to do a query where the product is in this category and it's on sale, you want to have the index take both of those into account to be super fast, right?
So, you have support for that?
Yeah, it supports also.
But it's not that neat, let's say.
It's not that beautiful.
But it supports it.
Yeah.
The payoff is worth it.
It's also in this class called collection, right?
So, it's kind of in its own special inner class of the model, in which case, you know, a lot of the IDs have a little chevron.
You can just collapse that thing and not look at it anymore.
So, it's easy to hide the complexity, I guess, there.
Yeah.
Cool.
All right.
What else?
Aggregation.
It sounds like that when we talk about, I'll have to get to it pretty quick.
When we talk about relationships and stuff, you said that this is super efficient because it's using the aggregation framework.
So, MongoDB has like two ways to query stuff, right?
It's like the straight query style.
And then it has something that's honestly harder to use but more flexible called aggregations.
And so, you guys support, your library supports aggregation queries as well, right?
Yeah.
And also, as before, with updates, it also supports find queries together with aggregations.
Like an example, for example, there are some presets of aggregations, average here.
And you can use this average with find queries together and you will see the result.
And also, for sure, you can pass a list pipeline in MongoDB terms.
You can pass pipeline of your aggregation steps there and it will work.
Yeah.
It's not super easy to write if you haven't done it before, but yes, it will work.
Yeah.
And also, the thing is, with aggregations, you have to set up what schema of the result
would be because with find, everybody knows it would be the same schema of the document,
of the original document.
But with aggregations, it definitely can be any schema of results.
Right.
Because the whole point of aggregation and other people might know something similar with
MapReduce is I want to take, say, a collection of sales and I want to get a result of show
me the sales by country and the total sales for that country.
Right.
So you're not going to get a list of sales back.
You're going to get a thing, a list of things that has a country and a total sales.
Right.
Yeah.
For sure.
It's optional and you cannot pass output model.
And in that case, it will return dictionaries, but it's not that fancy.
So it would be better to.
Yeah.
This is super cool.
I love this projection model idea.
Mario in the audience says, I created a model loader.
Speaking of the passing the documents to Beanie and init, I created a model loader as a utility
function that pulls dot separated pass and then passes it to document models.
Works really well.
Great.
There you go.
Right on.
So let's talk about relationships.
Because I started out talking about, you don't use the R, you use the D because you model documents,
not relationships.
And yet Beanie supports relationships.
I'm super excited about this.
Yeah.
Tell us about this.
Yeah.
So it took around three months to come up how to do relations.
MongoDB doesn't support relations.
But relations is very popular feature in Orium and ODIMs.
And I had to implement it finally.
And I did it.
For now, it's supported limited version of relations, like only top level fields are supported and
only two kinds of relations, direct relation and list of relations.
Right.
A one-to-one or a one-to-many, I guess.
One-to-one, one-to-many.
Yeah.
Uh-huh.
And so the syntax is by tonic, I'd say.
It uses generic class link inside of bracket, of square brackets, pass your document.
Right.
So maybe you would specify, normally you would say an optional int.
Here you would say like link int.
And that might, int doesn't make sense.
But, you know, that type thing would be the relationship, right?
It's like the same syntax as optional basically here.
Yeah.
It's a little bit tricky and with a black magic under the hood.
But as long as I don't have to know about it.
Thanks for creating the black magic.
So you could say here your model is there's a door and a house.
And then the door is of type link of door.
And then you have another one.
You have windows where the house has many windows.
And you would say the windows is a list of link of window, which is, it's a little bit
intense on the nesting there, but it's not bad, right?
It's just, it's a list of relationships.
Yeah.
And for sure it's possible.
And I think later it would be implemented.
I will, you know, shorten this list of link to another.
Links.
No, just kidding.
Don't do that.
Although it would be kind of awesome.
As a syntax, I think it would be less discoverable.
Yeah.
And it works.
You can insert data inside of this linked documents to linked collections.
And you can fetch data from linked collections.
Yeah.
And you can even have it cascade things.
So for example, you could have created a house object and say dot windows is this list of window
objects.
And then you would say house dot save.
And if you pass the link rule, then that says write the cascade the changes.
It'll also go and insert all those windows and associate them.
Right?
Yeah.
Correct.
And I didn't use cascade term because it's not SQL database and a little bit different
things.
Sure.
With relations, it would be a little different.
Yeah.
How does this look in the database itself?
So if I go to MongoDB and I pull up the house, what is in its windows?
Is that a list of the IDs of the window objects or what is that?
In MongoDB, there is a special data type called ref ID.
Okay.
It's combined under the hood.
It's binary and data type, but under the hood, it's a combination of ID of the document,
name of the collection and name of the database.
So it's a two point.
Oh, interesting.
Okay.
So that's what I, in the document, what I end up with is a list of those things.
Yeah.
Okay.
And you will see a few collections like here, house, window and door, three collections
with objects, separated objects.
Okay.
Cool.
You can also tell it that you don't want to propagate those changes, right?
As you save the house, which is interesting.
Let's talk about refetch.
So I told you one of the, when I see those websites that are just dragging super slow, I go through
my thing.
All right.
Did they forget the index?
Are they doing some terrible seven way join?
Probably without indexes.
Or the third thing.
Is it an N plus one ORM problem where they get one thing, but then they've got to go back
and back and back because they're touching this related field, which you could potentially run
into that problem as well.
So you have this prefetch idea, which is kind of like a joined load or something like that,
right?
Yeah.
It uses lookup aggregation in terms of MongoDB.
it's not just find query, but aggregation and it avoids this N plus one problem.
Yeah.
As you said.
Right.
So you just do, and you're fine.
You just say fetch links equals true.
And that'll just go get the doors, the windows, everything in your house example, right?
Yeah.
And I like the speed of this.
Like it's much faster than do it, you know, one by one, especially for list of objects.
Yeah, absolutely.
So you also have the ability to say fetch all links retroactively.
If you've been like, oh, I should have, I should have done this join, but I didn't.
Yeah.
That might sound silly, right?
Well, why not just always do the join, right?
That's one, probably slower than not doing it, I would guess.
And two, this happens to me all the time.
Like for example, on the courses website, I want to show, I want to be able to get the
courses, but the courses have like chapter information and other stuff inside of them.
And then those have like links effectively over to say like all the details about each chapter,
like the lectures and videos and all that.
On say the page that lists the courses, I do not want those things.
But on the course details page where it shows you like, here's all the stuff in the course
and how long it is, like I definitely do want those things.
So in my data access layer, I have a thing that says, should you get all the data or just
the top level data?
Basically.
And this would be exactly the code you'd write.
Like, well, if you want all the data, you'd say fetch all links on it, right?
Yeah.
Yeah, correct.
This is cool.
Is there a way to do that on a set?
Like this is on one record.
Is there a way to say I got 20 houses back, fetch all of their links?
Or do I have to do it?
Is that 20 calls?
So it would be 20 calls, unfortunately.
That's okay.
I think in my example, it's also 20 calls or however many.
The same setup is exactly the same.
But I will improve it.
I hope.
Yeah.
This is worth pointing out.
This is a brand new feature, right?
You announced this as one of the new features just two days ago.
On Monday, yeah.
Yeah.
And for the people listening, we're recording on Wednesday morning.
So yeah, this is not, this is like your first pass.
But I really like how this looks with the relationships and the querying and whatnot.
It would be nice, I think, if you could have some way to kind of globally configure to save.
In general, if I call save, the rule to write the relationships is to not, to do nothing or to always write them or something and then only have to override it potentially.
Yeah.
Sounds like default, default rule.
Yeah, exactly.
Exactly.
That'd be pretty cool.
So let's see a few other things we could talk about.
We talked a little bit about the event-based actions, but you want to just kind of talk about them directly because it was like a quick, well, how do I add my default value?
What's the overall picture with these default, with these event actions?
Yeah.
A lot of, somehow a lot of people wanted this.
And I didn't know about this pattern before.
Like it's implemented something like this, implemented in ActiveRap record pattern of Rails.
And so finally I was inspired by this.
I like this word inspired by.
Not stolen, but inspired by.
Exactly.
Yeah.
And we met this, yeah.
And now it's supported only four types of events.
There are events on each insert, replace, save, changes, and validate on save.
It creates an event and two events before it called and after it called.
And based on these events, actions already registered to the document would be called also.
It supports and asynchronous and asynchronous methods for this.
And you can do a lot of stuff with this.
Yeah, that's cool.
You can put the decorator on just an async version or a non-async version and being able to just call it correctly, right?
Yeah.
Yeah, that's cool.
I'm guessing if you're not doing any awaits, it'd be better if it was not asynchronous, but it doesn't matter that much, right?
It's one and a half faster if it's not asynchronous.
Yeah.
So probably it's critical for something.
Yeah, if it's doing something where it's waiting on something else, then maybe it should be.
Yeah.
Okay.
Another feature that just came out is cache.
Yeah, cache.
Tell us about this.
It's also an interesting feature.
Like, so yeah, it's cache.
I don't know if I have to explain what is cache.
It's when you save data somewhere locally and use a copy of data for some time.
Yeah, and what you're talking about is not using MongoDB as a caching backend, but caching the queries that would run through Beanie to not hit the database again if it knows the answer.
Yeah.
Yeah, and somehow it's a really important feature even for my projects.
For example, if you have to validate stuff with user and you already asked for a user in this application with this ID, but you don't know the place where you did it and you don't want to provide this.
It's an object through the whole pipeline because probably it will not be used in the end of this pipeline.
But user is already cached in Beanie.
And if you just will ask in the end of the pipeline again about the same user data, so it would be there.
And with a bigger field of find queries, it works the same way.
Yeah, the more complicated the query, the better.
This makes a lot of sense, I think, for data that doesn't change much.
Like if you've got a bookstore, you might have categories and books.
And maybe the books change often.
The reviews of the books change often.
The books that are in different categories change.
But the categories themselves very rarely change, right?
So that could be something like the category query could just be like, you know what?
This is cached.
Yeah, definitely.
So for now, it supports only local cache like dictionaries of Python.
But I plan to add another cache backends like Redis and things like this.
Yeah, that's cool.
Or even possibly you could put, it might even make sense to use MongoDB itself as a cache.
Yeah.
Because it's more native.
I mean, it'd be weird to kind of store the same thing back into it.
But at the same time, if you've got a complicated query, what you're storing is like, these are the three things that came back from running that query against possibly hundreds of millions of records.
Right?
So in that case, it might make sense to just put it back in Mongo so it's just a straight, you know, table scan read.
Something like Lambda architecture inside of the single database.
Yeah.
So I wanted to ask you, your example says, okay, what we're going to do is say sample.find num greater than 10 to list.
And then if you call it again with the caching on, then you get the same thing.
Well, it looks at the actual query, right?
So if I said num greater than 11, I would get, that would be a separate result and a separate cached thing, right?
Yeah.
Yeah, totally.
Cool.
The other thing I guess that's worth noting is you can set an expiration date on the cache, right?
Like I want this to live for 10 minutes or whatever to get the answer back.
Yeah.
Cool.
All right.
Let's talk about revisions.
And then I'm going to propose one more idea that I think I could build out of revisions and events.
So what are revisions?
So it's not my idea again.
It's neither one user asked me for this.
And I really like users of Winnie because I have not that many ideas.
And yeah, what is this?
You, sometimes you have to protect document inside of the database of changes.
So yeah, sometimes you have old version of the data in your backend and you do some updates.
And if you update your document with this old data, you will lose data updated by another
backend for the same document.
So let me, let me give people some examples because I think understanding the context is
really important.
Like it could be even the same function basically.
Right.
So I could have a function that do complicated things.
I could say, get me my user object for the current user who wants to make some changes.
I could call, do a bunch of work, call a function with the past, say like the user ID over.
Maybe that gets the user back, makes some changes, saves it to the database.
And then I go to the end, I make some more changes, not realizing that to my in-memory version.
And I call save and it overwrote what that intermediate function might have, whatever was there is gone
now.
You know what I mean?
So you would want to know, is there, is the thing that I got back, if I'm going to replace
it in the database, is it still the same thing or somebody somewhere behind the scenes changed
it?
You know, often I think that people think about very complicated.
Well, some other process did some other thing, but it could just be some other part of your
code that you didn't realize called save after a query.
Yeah.
And for this case, I'm using a revision ID, a special token, which
generates each time when data saves into the database.
And when you save again, it will check if this ID is the same or it's already updated.
If it's updated, it will raise an error, like you have all data in memory, but if it's the
same, it will allow you to.
Nice.
That's really cool.
I think it's a great feature.
It's good.
It definitely is.
Because the alternative of this pattern is to do a blocking transaction.
Yeah.
Right.
And that's also potentially possible.
I think MongoDB does have transactions now, but I still haven't used them.
I don't really have a use for that.
But the alternative model in databases is to say, we're going to do a transaction that blocks
and anyone else who tries to do a database thing whatsoever, they just wait until we're
done.
And that way, there's no chance of them seeing it in this intermediate state.
A lot of the scalable systems don't end up doing that even in relational databases because
this blocking model can really kill the concurrency.
Yeah.
Right.
And so they end up doing optimistic concurrency with these types of revisions.
Anyway, it's just so I think it's a really cool pattern.
I love it.
Yeah.
This is great.
It's great.
It's not my idea, but it's great.
It's really good.
It's also, I guess, worth discussing set and increment and those types of things.
So if I say I want to add a category to a product, I could do add to set on that thing
and just put this thing in its category list.
Will that also increment the revision?
So only set, I think, yeah.
Because if you will use internal methods of MongoDB, it will not understand that it needs
to update another field.
Got it.
Okay.
Interesting.
But yeah, that's a good feature.
I like it.
This is a big release.
It's a huge one.
Cool.
Well, we've been talking for a long time.
As you can tell, I'm very excited about it.
What else?
What's coming next?
So next, I have a big plan sales.
I really like Pydantic.
I'm a huge fan of Pydantic.
You can see it.
But for some cases, Pydantic is a heavy tool.
And probably, I don't know how I will implement it, but I want to add support of native Python
data classes here or here or to separate it smaller project like Bini data classes.
I don't know yet.
But anyway, I have planned to add native classes without Pydantic.
Just with not, let's say, fancy parsing stuff without that great validation stuff.
But somewhere in between, I just want to, you're just getting dictionaries back.
Good luck.
And you're getting Pydantic.
Somewhere in the middle is you're getting classes back, but they don't necessarily, they're not
as precise as, say, Pydantic.
Yeah.
There are cases.
And people use Bini with these cases also.
When you have a lot of huge JSONs as a document, and when you parse it on a fetching, it keeps
a lot of time.
Yeah.
So it takes a lot of time just for parsing.
And then for encoding back to dictionary to store this dictionary.
Yeah.
It's not Pydantic fault, definitely, because Pydantic is, again, it's great.
But I need to avoid this step somehow.
And probably, probably I will use data classes for this.
Sure.
They look very similar.
Yeah.
Another scenario where I find that Pydantic is not a good fit is where I might be getting
bad data, but I don't want it just to be an exception.
I want to be able to get all the bad data and say, here's the three errors that you made
passing me this data.
And I'm not going to accept it, but here's what you gave me.
If you're doing like form exchange, right?
Like from an HTML form.
What you need to do is put the old value back in and say, that value right there, that's wrong.
But with Pydantic, if you get the value from the form and you try to read it, it's just
going to know it's wrong.
And you're like, wait, but I need to give it back to them.
Just don't run away.
Come back.
Where'd you go?
And so there's situations like that where you need to kind of keep that exchange going,
but you still want some sort of, you just got to do the validation yourself.
But anyway, there's certainly times where Pydantic is as cool as it is, it's not the right fit
for that situation.
Yeah.
I agree.
Totally.
Good example.
Thanks.
All right.
Well, this is a very cool project.
I've, as you hinted at earlier, I've seen it from the beginning, at least when you open
sourced it and it's really come a long ways.
It's super compelling.
It looks like something that I could possibly use on my next project.
I constantly, as many people out there are, I'm sure as you are, I'm constantly resisting
the urge to go, you know, I should rewrite that.
I should rewrite that in FastAPI or I should rewrite that in this.
I should rewrite my Mongo Engine stuff with Beanie, but you know, maybe one day I'll break
down and do it.
It'd be fun.
Yeah.
Thank you.
Yeah.
Very cool.
So nice work on this project.
You're looking for contributors and PR.
Would you be, PRs, would you be happy to have people make contributions?
I make a bunch of issues now.
A lot of people found some, something doesn't fit to different use cases.
And so, yes, it would be great to have other contributors here.
I already have 15 contributors.
Yeah.
That's why I saw there's quite a few people in the sidebar there.
Yeah.
That's awesome.
Yeah.
But I need more, more and more.
And especially for documentation, because I don't know, I really much better in Python
than in English.
And documentation is my weak point.
Yeah.
Okay.
Cool.
Well, definitely a neat project.
So thank you for building it.
Now, before you get out of here, you have to answer the final two questions.
If you're going to write some Python code, what editor are you using these days?
What did you use to create Beanie with, for example?
I'm using Pesharm.
JetBrains also gave me ProVersion for Beanie as support program, I think.
Yeah.
Fantastic.
It's really great tool.
I'm expecting a new one I don't remember the name of, but so they have a new idea.
I don't remember the name.
Fleet.
Yeah.
I was about to mention Fleet.
Fleet is interesting.
Fleet is like JetBrains' response to VS Code.
Yeah.
I'm pretty excited about it.
You know, I really love PyCharm.
Nobody's going to be surprised that I say that.
But if I've got just one file, like I'm going to open that in VS Code because all the JetBrains
IDE tools, they expect like a project and they're going to create all these things.
I'm like, I just want to look at the files.
Just the files, please.
Like just not too much.
And this is kind of like that where you can later turn on some of the IDE features, right?
It looks pretty cool.
You're going to try it out?
Not yet.
I asked for it, but so JetBrains, if you hear me, please.
Yes, exactly.
JetBrains.
I'm already on the early access list too.
And I have an email.
Maybe I haven't checked it this morning.
Maybe it's there.
Fantastic.
Yeah.
All right.
So PyCharm, maybe Fleet in the future, potentially.
Cool.
And then notable PyPI package.
I mean, we've talked about a bunch of libraries already.
Yeah.
So I really like one.
It's not about Pydanticum or FastAPI stuff, but it's a great package called YARL.
Y-A-R-L.
Yeah.
It's like a pass library, but for URLs.
And it's great.
You can combine strings with this URL stuff together and you can parse it.
You can...
Oh, that's cool.
Yeah.
So you can pass it a URL as a string, just like whatever you'd expect.
But then you can say, give me the scheme, which is like HTTP, HTTPS, the host, the path,
the query string.
Yeah.
I really like how they use this divide operator.
You probably see it in the bottom of your page now.
Like URL, divide full, divide bar.
Oh, interesting.
It's like Pathlib style.
It's like Pathlib, yeah.
It's like Pathlib for URLs.
Cool.
It's super great.
All right.
This is totally new to me.
Awesome.
Good recommendation.
Yeah.
I use it in each project now.
I don't know how to live without this.
All right.
Well, I'm going to check it out for sure.
One quick follow from the audience here.
Mario says, thank you for this project.
I'm about to launch my FastAPI Beanie blog soon.
Couldn't have done it without it.
Thank you.
Yeah.
And Ollie's on it.
He says, Pathlibs for URLs.
Indeed, it's Pathlib for URLs.
Cool.
That's a great one.
All right.
Final call to action.
People want to get started with Beanie.
What do you say?
I would say, have fun.
Awesome.
I would recommend that people go and check out the, if you go to the documentation, there's
a tutorial that walks you through this pretty well right there on the left.
It just starts by defining a document and then initialization and so on.
Yeah.
We try to do this as much simple to understand as possible.
Fantastic.
All right.
Roman, thank you for being here.
Thank you very much for being here.
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.
Take some stress out of your life.
Get notified immediately about errors and performance issues in your web or mobile applications with
Sentry.
Just visit talkpython.fm/sentry and get started for free.
And be sure to use the promo code talkpython, all one word.
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.