You're using Pydantic and it seems pretty straightforward, right?
But could you adopt some simple changes to your code that would make it a lot faster and more
efficient? Chances are you'll find a couple of the tips from Sydney Runkle that will do just that.
Join us to talk about Pydantic performance tips here on Talk Python, episode 466, recorded June 13th,
2024. Are you ready for your host? There he is. You're listening to Michael Kennedy on Talk Python
to me. Live from Portland, Oregon, and this segment was made with Python.
Welcome to Talk Python to Me, a weekly podcast on Python. This is your host, Michael Kennedy.
Follow me on Mastodon, where I'm @mkennedy and follow the podcast using @talkpython,
both on fosstodon.org. Keep up with the show and listen to over seven years of past episodes at
Talk Python.fm. We've started streaming most of our episodes live on YouTube. Subscribe to our
YouTube channel over at talkpython.fm/youtube to get notified about upcoming shows
and be part of that episode. This episode is brought to you by Sentry. Don't let those errors
go unnoticed. Use Sentry like we do here at Talk Python. Sign up at talkpython.fm/sentry.
And it's brought to you by Code Comments, an original podcast from Red Hat. This podcast covers
stories from technologists who've been through tough tech transitions and share how their teams
survive the journey. Episodes are available everywhere you listen to your podcasts and
at talkpython.fm/code dash comments. Hey folks, I got something pretty excellent for you.
PyCharm Professional for six months for free. Over at Talk Python, we partnered with the JetBrains
team to get all of our registered users free access to PyCharm Pro for six months. All you have to do is
take one of our courses. That's it. However, do note that this is not valid for renewals over at
JetBrains. Only new users there. And if you're not currently a registered user at Talk Python, well,
no problem. This offer comes with all of our courses. So even if you just sign up for one of our free
courses at talkpython.fm, click on courses in the menu, you're in. So how do you redeem it? Once you have
an account over at Talk Python, then it's super easy. Just visit your account page on Talk Python
training. And in the details tab, you'll have a code and a link to redeem your six months of PyCharm
Pro. So why not take a course, even a free one and get six months free of PyCharm.
Sydney, welcome back to Talk Python to me. It's awesome to have you here.
Thank you. Super excited to be here. And yeah, I'm excited for our chat.
I am too. We're going to talk about Pydantic, one of my very favorite libraries that
just makes working with Python data, data exchange so, so easy, which is awesome. And it's really
cool that you're on the Pydantic team these days. Before then, I guess, you know, let's jump back
just a little bit. A few weeks ago, got to meet up a little bit in Pittsburgh at PyCon. How was PyCon
for you?
It was great. So it was my first PyCon experience ever. It was a very, very large conference. So it
was a cool kind of first introductory conference experience. I had just graduated not even a week
before. So it was a fun way to kind of roll into full time work and get exposed really to the Python
community. And it was, it was great to just kind of have a mix of getting to give a talk, getting to
attend lots of awesome presentations, and then most of all, just like meeting a bunch of really awesome
people in the community.
Yeah, I always love how many people you get to meet from so many different places and perspectives
and it's, it just reminds you the world is really big, but also really small, you know,
get to meet your friends and new people from all over the place.
Definitely. I was impressed by the number of like international attendees. I didn't really expect
that. It was great.
Yeah, same, same here. All right. Well, maybe a quick introduction for yourself, for those who
didn't hear your previous episode, and then we'll, we'll talk a bit about this Pydantic library.
Yeah, sure. Sounds great. So my name is Sydney. I just graduated from the University of Wisconsin.
Last time I chatted with you, I was still pursuing my degree in computer science and working part time as an
intern at the company Pydantic, which is kind of founded around the same ideas that inspired the open source tool.
And now we're building commercial tools and now I've rolled over into full-time work with them, primarily on the open source side.
So yeah, very excited to kind of be contributing to the open source community, but also getting to help with our commercial tools and development there.
Yeah. Yeah. Awesome. We'll talk a bit about that later. Super cool to be able to work on open source as a job, as a proper job, right?
Yeah. It's, it's awesome. It's really unique. I've kind of encouraged lots of people to contribute to open source as kind of a jumpstart into their software development careers, especially like young folks who are looking to get started with things and maybe don't have an internship or that sort of thing set up yet. I think it's a really awesome pipeline for like getting exposed to good code and collaborating with others and that sort of thing. But it's definitely special to get to do and get paid as well.
Indeed. So it's, it's a little bit unbelievable to me, but I'm sure that it is true that there are folks out there listening to the podcast that are like, Pydantic, maybe you've heard of that. What is, what is this Pydantic thing?
Yeah. What is Pydantic?
So Pydantic is the leading data validation library for Python. And so Pydantic uses type hints, which are optional in Python, but kind of generally more and more encouraged to enforce constraints on data and kind of validate data structures, etc.
So we're kind of looking at a very simple example together right now, where we're importing things like date time and tuple types from typing. And then kind of the core of Pydantic is you define these classes that inherit from this class called base model that's in Pydantic.
And that inheritance is what ends up helping you use methods to validate data, build JSON schema, things like that.
And so in our case, we have this delivery class that has a timestamp, which is of type date time, and then a dimensions tuple, which has two int parts.
And so then when you pass data into this delivery class to create an instance, Pydantic handles validating that data to make sure that it conforms to those constraints we've specified.
And so it's really a kind of intermediate tool that you can use for deserialization or loading data and then serialization, dumping data.
Yeah, it's a thing of beauty. I really love the way that it works. If you've got JSON data, nested JSON data, right?
If you go to Pydantic.dev slash open source, there's an example of here that we're talking about. And it's got a tuple, but the tuple contains integers, two of them.
And so if there's a tuple of three things, it'll give you an error. If it's a tuple of a date time in an int, it'll give you an error.
Like it reaches all the way inside. And, you know, things I guess it compares against. It's a little bit like data classes. Have you done much with data classes and compare them?
Yeah, that's a great question. So we actually offer support for like Pydantic data classes.
So I think data classes kind of took the first step of, you know, really supporting using type hints for model fields and things like that.
And then Pydantic sort of takes the next jump in terms of like validation and schema support.
And so I think one like very common use case is if you're defining like API request and response models, you can imagine like the JSON schema capabilities come in handy there.
And just ensuring like the integrity of your API and the data you're dealing with. Very helpful on the validation front.
Yeah, yeah. Very cool. Okay. Well, I guess one more thing for people who are not super familiar that Pydantic is, I think it's used every now and then.
Let's check it out on GitHub here. I'm just trying to think of, you know, like some of the main places people have heard of it.
Obviously, FastAPI, I think is the thing that really launched its popularity in the early days, if I had to guess.
But if we go over to GitHub, GitHub says that for the open source things that Pydantic is a foundational dependency for 412,644 different projects.
Yeah.
That's unbelievable.
Yeah, it's very exciting. We just got our May download numbers and heard that we have over 200 million downloads in May.
So that's both version one and version two, but definitely exciting to see how kind of critical of a tool it's become for so many different use cases in Python, which is awesome.
Yeah, absolutely. It's really, really critical.
And I think we should probably talk a little bit about Pydantic v1, v2 as a way to get into the architecture conversation, right?
That was a big thing. I talked to Samuel Colvin maybe a year ago or so, I would imagine.
I think around PyCon, I think we did actually at PyCon last year as well.
Yeah, for sure. So a lot of the benefit of using Pydantic is we promise some great performance.
And a lot of those performance gains came during our jump from v1 to v2.
So v1 was written solely in Python. We had some compiled options, but really it was mostly Pythonic data validation.
Or I say Pythonic, it's always Pythonic, but data validation done solely in Python.
And the big difference with v2 is that we rewrote kind of the core of our code in Rust.
And so Rust is much faster.
And so depending on what kind of code you're running, v2 can be anywhere from two to 20 times faster in certain cases.
So right now we still have this Python wrapper around everything in v2.
But then, and that's kind of used to define schemas for models and that sort of thing.
And then the actual validation and serialization logic occurs in Pythonic core in Rust.
Right. So I think the team did a really good job to make this major change, this major rewrite,
and split the whole monolithic thing into a Pythonic core, Pythonic itself, which is Python-based,
in a way that didn't break too many projects, right?
Yeah, that was the goal.
You know, every now and then there are breaking changes that I think are generally a good thing for the library moving forward, right?
Like hopefully whenever we make a breaking change, it's because it's leading to a significant improvement.
But we definitely do our best to avoid breaking changes.
And certainly someday we'll launch a v3 and hopefully that'll be an even more seamless transition for v2 users to v3 users.
Yeah, I would imagine that the switch to Rust probably, that big rewrite,
it probably caused a lot of thoughts of reconsidering, how are we doing this?
Or now that it's over in Rust, maybe it doesn't make sense this way or whatever.
Yeah. And I think just kind of, you know, we got a lot of feedback and usage of Pythonic v1,
so tried to do our best to incorporate all that feedback into a better v2 version in terms of both APIs and performance and that sort of thing.
Sure, sure.
John out in the audience asks, how do the team approach thread safety with this?
So Rust can be multiple threads, easy.
Python, not so much really, although maybe soon with free-threaded Python.
Yeah, that's a good question.
So our kind of Rust guru on the team is David Hewitt.
And he's very in the know about all of the multi-threading and things happening on the Rust side of things.
I myself have some more to learn about that, certainly.
But I think in general, kind of our approach is that Rust is quite type-safe, both performant and type-safe,
which is great and memory-safe as well.
And I think most of our...
I'll talk a little bit later about some, like, parallelization and vectorization that we're looking at for performance improvements.
But in terms of safety, I think if you have any questions, feel free to open an issue on the Pydantic core repo and get a conversation going with David Hewitt.
I would imagine it's not...
You guys haven't had to do too much with it.
just that Python currently, but soon, but currently doesn't really let you do much true multi-threading because of the GIL.
But the whole...
I think, you know...
Yeah, I think Python 3.13 is going to be crazy with free-threaded Python, and it's going to be interesting to see how that evolves.
Yep.
Anyway.
Yeah, I know we definitely do some jumping through hoops and just, you know, having to be really conscious of stuff with the GIL in Pydantic core and Py03.
And Py03 is kind of the library that bridges Python and Rust, and so it's heavily used in Pydantic core, as you can imagine.
So I'm excited to see what changes might look like there.
Yeah, same.
All right, well, let's jump into the performance because you're here to tell us all about Pydantic performance tips.
And you got a whole bunch of these.
Did you give this talk at PyCon?
I did, partially.
It's a little bit different, but some of the tips are the same.
I don't think the videos are out yet, are they?
As the time of recording on June 13th.
Yeah.
No, I actually checked a couple of minutes ago.
I was like, I said one thing during my talk that I wanted to double check, but the videos are not out yet.
No, I'm really excited.
There's going to be a bunch.
There was actually a bunch of good talks, including yours and some others.
I want to watch, but they're not out yet.
All right.
Let's jump into Pydantic performance.
Where should we start?
I can start on the slideshow if we want.
Yeah, let's do that.
Awesome.
So, yeah, I think kind of the categories of performance tips that we're going to talk about here kind of have some like fast one-liner type performance tips that you can implement in your own code.
And then kind of the meat of the, like, how do I improve performance in my, in my, you know, application that uses Pydantic, we're going to talk a bit about discriminated unions, also called tag unions, and then kind of finally talk about on our end of the development, how are we continuously improving performance, you know, Pydantic internals wise, etc.
Sure. Do you have the equivalent of unit tests for performance?
Yeah, we do.
Okay.
We use a library called codspeed that I'm excited to touch on a bit more later.
Yeah, all right. Let's talk about that later. Perfect.
Yeah, sure thing. So I have this slide up right now, just kind of talking about why people use Pydantic. We've already covered some of these, but just kind of as a general recap, it's powered by type hints. And one of our biggest promises is speed.
We also have these other great features like JSON schema compatibility and documentation comes in particularly handy when we talk about APIs, you know, support for custom validation and serialization logic.
And then as we saw with the GitHub repository observations, a very robust ecosystem of libraries and other tools that use and depend on Pydantic that leads to this kind of extensive and large community, which is really great.
But this all kind of lies on the foundation of like Pydantic is easy to use and it's very fast.
Yeah.
So let's talk some more.
And this, yeah, well, the speed is really interesting in the multiplier that you all have for basically a huge swath of the Python ecosystem, right?
We just saw the 412,000 things that depend on Pydantic.
Well, a lot of those, their performance depends on Pydantic's performance as well, right?
Yeah, certainly.
Yeah, it's nice to have such a large ecosystem of folks to also, you know, contribute to the library as well, right?
Like, you know, because other people are dependent on our performance, the community definitely becomes invested in it as well, which is great.
This portion of Talk Python is brought to you by OpenTelemetry support at Sentry.
In the previous two episodes, you heard how we use Sentry's error monitoring at Talk Python and how distributed tracing connects errors, performance and slowdowns and more across services and tiers.
But you may be thinking, our company uses OpenTelemetry, so it doesn't make sense for us to switch to Sentry.
After all, OpenTelemetry is a standard and you've already adopted it, right?
Well, did you know, with just a couple of lines of code, you can connect OpenTelemetry's monitoring and reporting to Sentry's backend.
OpenTelemetry does not come with a backend to store your data, analytics on top of that data, a UI or error monitoring.
And that's exactly what you get when you integrate Sentry with your OpenTelemetry setup.
Don't fly blind.
Fix and monitor code faster with Sentry.
Integrate your OpenTelemetry systems with Sentry and see what you've been missing.
Create your Sentry account at talkpython.fm/sentry dash telemetry.
And when you sign up, use the code TALKPYTHON, all caps, no spaces.
It's good for two free months of Sentry's business plan, which will give you 20 times as many monthly events as well as other features.
My thanks to Sentry for supporting Talk Python.
But yeah, so kind of as that first category, we can chat about some basic performance tips.
And I'll do my best here to kind of describe this generally for listeners who maybe aren't able to see the screen.
So when you are validating...
Can we share your slideshow later with the audience?
Can we put it in the show notes?
Yeah, yeah, absolutely.
Okay, so people want to go back and check it out.
But yeah, we'll describe it for everyone.
Go ahead.
So when you're validating data in Pydantic, you can either validate Python objects or like dictionary type data, or you can validate JSON formatted data.
And so one of these kind of like one liner tips that we have is to use our built in model validate JSON method instead of calling this our model validate method and then separately loading the JSON data with the standard lib JSON package.
And the reason that we recommend that is one of the like crux of the general performance patterns that we try to follow is not materializing things in Python when we don't have to.
So we've already mentioned that our core is written in Rust, which is much faster than Python.
And so with our model validate JSON built in method, whenever you pass in that string, we send it right to Rust.
Whereas if you do the JSON loading by yourself, you're going to like materialize Python object and then have to send it over.
Right.
And so you're going to be using the built in JSON load S, which will then or load or whatever, and then it'll pull that in, turn it into a Python dictionary.
Then you take it and try to convert that back to a Rust data structure and then validate it in Rust.
That's where all the validation lives anyway.
So just get out of the way, right?
Exactly.
Yep.
It's like skip the Python step if you can.
Right.
And I will note there is one exception here, which is I mentioned we support custom validation.
If you're using what we call like before and wrap validators that do something in Python and then call our internal validation logic and then maybe even do something after.
It's OK.
You can use model validate and the built in JSON dot load S because you're already kind of guaranteed to be materializing Python objects in that case.
But for the vast majority of cases, it's great to just go with the built in model validate JSON.
Yeah, that's really good advice.
And they seem kind of equivalent.
But once you know the internals, right, then it's well, maybe it's not exactly.
Yeah.
And I think implementing some of these tips is helpful in that if you understand some of the kind of like pydantic architectural context, it can also just help you think more about like, how can I write my pydantic code better?
Absolutely.
So the next tip I have here, very easy one liner fix, which is when you're using a type adapter, which is this structure you can use to basically validate one type.
So we have base models, which we've chatted about before, which is like if you have a model with lots of fields, that's kind of the structure you use to define it.
Well, type adapter is great if you're like, I just want to validate that this data is a list of integers, for example, as we're seeing on the screen.
Right.
Because let me give people an idea.
Like if you accept if you've got a JSON, well, just JSON data from wherever.
But, you know, a lot of times it's coming over an API or it's provided to you as a file and it's not your data you control, right?
You're trying to validate it.
You could get a dictionary JSON object that's got curly braces with a bunch of stuff, in which case that's easy to map to a class.
But if you just have JSON, which is bracket thing, thing, thing, thing, close bracket.
Well, how do you have class that represents a list?
Like it gets really tricky, right, to be able to understand.
You can't model that with classes.
And so you all have this type adapter thing, right?
That's what the role plays generally.
Is that right?
Yeah.
And I think it's also really helpful in a testing context.
Like, you know, when we want to check that our validation behavior is right for one type, there's no reason to go like build an entire model.
If you're really just validating against one type or structure, type adapter is great.
And so kind of the advice here is you only want to initialize your type adapter object once.
And the reason behind that is we build a core schema in Python and then attach that to a class or type adapter, etc.
And so if you can, you know, not build that type adapter within your loop, but instead of instead do it right before or not build it, you know, in your function, but instead outside of it, then you can avoid building the core schema over and over again.
Yeah.
So basically what you're saying is that the type adapter that you create might as well be a singleton because it's stateless, right?
Like it doesn't store any data.
It's kind of slightly expensive to create relatively.
And so if you had a function that was called over and over again and that function had a loop and inside the loop, you're creating the type adapter, that'd be like worst case scenario almost, right?
Yeah, exactly.
And I think this kind of goes along with like general best programming tips, right?
Which is like, if you only need to create something once, do that once.
Exactly.
You know, a parallel that maybe goes way, way back in time could be like a compiled regular expression.
You know, right.
You wouldn't do that over and over in a loop.
You would just create a regular, the compiled regular expression and you use it throughout your program, right?
Because it's kind of expensive to do that, but it's fast once it's created.
Yeah, exactly.
And funny that you mentioned that.
I actually fixed a bug last week where we were compiling regular expressions twice when folks like specified that as a constraint on a field.
So definitely just something to keep in mind and easy to fix or implement with type adapters here.
Yeah.
Awesome.
Okay.
I like this one.
That's a good one.
Yeah.
So this next tip also kind of goes along with like general best practices, but the more specific you can be with your type hints, the better.
And so specifically, if you know that you have a list of integers, it's better and more efficient to specify a type hint as a list of integers instead of a sequence of integers, for example.
Or if you know you have a dictionary that maps strings to integers, specify that type hint as a dictionary, not a mapping.
Interesting.
Yeah.
So you could import a sequence from the typey module, which is the generic way.
But I guess you probably have specific code that runs that can validate lists more efficiently than a general iterable type of thing, right?
Yeah, exactly.
So in the case of like a sequence versus a list, it's the like square and rectangle thing, right?
Like a list is a sequence, but there are lots of other types of sequences.
And so you can imagine for a sequence, we like have to check lots of other things.
Whereas if you know with certainty, this is going to be a list or it should be a list, then you can have things be more efficient with specificity there.
Does it make any difference at all whether you use the more modern type specifications?
Like traditionally, people would say from typing import capital L list, but now you can just say lowercase L list with the built in and no import statement.
Are those equivalent or is there some minor difference there?
Do you know?
Yeah, that's a good question.
I wouldn't be surprised if there was a minor difference that was more a consequence of like Python version, right?
Because there's like, I mean, I suppose you could import the old capital L list in a newer Python version.
But I think the difference is like more related to specificity of a type hint rather than kind of like versioning.
Yeah.
If the use of that capital L list made you write an import statement, I mean, it would cause the program to start ever so slightly slower.
Cause there's another import.
It's got to run worse.
It already knows.
It's already imported.
What list is.
You wouldn't believe how many times I get messages on YouTube videos I've done, or even from courses saying, Michael, I don't know what you're doing, but your code is just wrong.
I wrote lowercase L list bracket something.
And it said list is not a sub indexable or something like that.
And look, you've just done it wrong.
You're going to need to fix this.
Or, or you're on Python 3.7 or something super old before these new features were added.
But there's just somewhere in the community.
We haven't communicated this well.
I don't know.
Yeah, for sure.
I was writing some code earlier today in a meeting and I used the, like, from typing import union and then union X and Y types.
And my coworker was like, Cindy, use the pipe.
Like, what are you doing?
Use the, exactly.
But here's the thing that was introduced in 3.10, I believe.
And if people are in 3.9, that code doesn't run.
Or if they're not familiar with the changes, it's, so there's all these trade-offs.
I almost feel like it would be amazing to go back for any time there's a security release that releases, say, another 3.7 or something.
And change the error message to say, this feature only works in the future version of Python rather than some arbitrary error if you're doing it wrong.
You know, that would be great.
Yeah, definitely.
Yeah, some of those errors can be pretty cryptic with the syntax stuff.
And they can.
All right.
So be specific.
List tuple, not sequence if you know it's a list or a tuple or whatever.
Yeah.
And then kind of my last minor tip, which great that you brought up import statements and kind of adding general time to a program, is I don't have a slide for this one.
But if we go back to the type adapter slide, we talked about the fact that initializing this type adapter builds a core schema and attaches it to that class.
And that's kind of done at build time, at import time.
So that's, like, already done.
And if you really don't want to have that import or, like, build time take a long time, you can use the defer build flag.
And so what that does is defers the core schema build until the first validation call.
You can also set that on model config and things like that.
But basically, the idea here is, like, striving to be lazier, right?
I see.
Like, if we don't need to build this core schema right at import time because we want our program to start up quickly, that's great.
We might have a little bit of a delay on the first validation, but maybe startup time is more important.
So that's a little bit more of a preferential validation.
Sorry, preferential performance tip, but available for folks who need it.
Yeah.
Like, let me give you an example.
Let's give people an example where I think this might be useful.
So in the Talk Python training, the courses site, I think we've got 20,000 lines of Python code, which is probably more at this point.
I checked a long time ago, but a lot.
And it's a package.
And so when you import it, it goes and imports all the stuff to, like, run the whole web app.
But also little utilities like, oh, I just want to get a quick report.
I want to just access this model and then use it on something real quick.
It imports all that stuff so that app startup would be potentially slowed down by this.
Where if you know, like, only sometimes is that type adapter used, you don't want to necessarily have it completely created until that function gets called.
So then the first function call might be a little slow, but there'd be plenty of times where maybe it never gets called, right?
Yep, exactly.
Yeah, awesome.
Okay.
All right.
So kind of a more complex performance optimization is using tagged unions.
They're still pretty simple.
It's just like a little bit more than a one line change.
So kind of talking about tagged unions, we can go through a basic example why we're using tagged unions in the first place and then some more advanced examples.
This portion of Talk Python to me is brought to you by Code Comments, an original podcast from Red Hat.
You know, when you're working on a project and you leave behind a small comment in the code, maybe you're hoping to help others learn what isn't clear at first.
Sometimes that Code Comment tells a story of a challenging journey to the current state of the project.
Code Comments, the podcast, features technologists who've been through tough tech transitions and they share how their teams survived that journey.
The host, Jamie Parker, is a Red Hatter and an experienced engineer.
In each episode, Jamie recounts the stories of technologists from across the industry who've been on a journey implementing new technologies.
I recently listened to an episode about DevOps from the folks at Worldwide Technology.
The hardest challenge turned out to be getting buy-in on the new tech stack rather than using that tech stack directly.
It's a message that we can all relate to, and I'm sure you can take some hard-won lessons back to your own team.
Give Code Comments a listen.
Search for Code Comments in your podcast player or just use our link, talkpython.fm/code dash comments.
The link is in your podcast player's show notes.
Thank you to Code Comments and Red Hat for supporting Talk Python to me.
Let's start with what are tag unions because I honestly have no idea.
I know what unions are, but tagging them, I don't know.
Yeah, sure thing.
So tag unions are a special type of union.
We also call them discriminated unions.
And they help you specify a member of a model that you can use for discrimination in your validation.
So what that means is if you have two models that are pretty similar and your field can be either one of those types of models, model X or model Y.
But you know that there's one tag or discriminator field that differs.
You can specifically validate against that field and skip some of the other validation, right?
So I'll move on to an example here in a second.
But basically, it helps you validate more efficiently because you get to skip validation of some fields.
So it's really helpful if you have models that have like 100 fields, but one of them is really indicative of what type it might be.
I see. So instead of trying to figure out like, is it all of this stuff, once you know it has this aspect or that aspect,
then you can sort of branch on a path and just treat it as one of the elements of the union.
Is that right?
Yes, exactly.
And so one other note about discriminated unions is you specify this discriminator and it can either be a string, like literal type or callable type.
And we'll look at some examples of both.
So here's kind of a more concrete example so we can really better understand this.
So let's say we have a, this is the classic example, right?
A cat model and a dog model.
And they both have...
Cat people, dog people, you're going to start a debate here.
Exactly, exactly.
They both have this pet type field.
And for the cat model, it's a literal that is just the string cat.
And then for the dog model, it's the literal that's the string dog.
So it's just kind of a flag on a model to indicate what type it is.
And you can imagine, you know, in this basic case, we only have a couple of fields attached to each model.
But maybe this is like data in a, like vet database.
And so you can imagine like there's going to be tons of fields attached to this, right?
So it'd be pretty helpful to just be able to look at it and say, oh, the pet type is dog.
Let's make sure this data is valid for a dog type.
Also note, we have a lizard in here.
So what this looks like in terms of validation with Pydantic then is that when we specify this pet field, we just add one extra setting, which says that the discriminator is that pet type field.
And so then when we pass in data that corresponds to a dog model, Pydantic is smart enough to say, oh, this is a discriminated union field.
Let me go look for the pet type field on the model and just see what that is.
And then use that to inform my decision for what type I should validate against.
Okay, that's awesome.
So if we don't set the discriminator keyword value in the field for the union, it'll still work, right?
It just has to be more exhaustive and slow.
Yeah, exactly.
So it'll still validate and it'll say, hey, let's take this input data and try to validate it against the cat model.
And then Pydantic will come back and say, oh, that's not a valid cat.
Like, let's try the next one.
Whereas with this discriminated pattern, we can skip right to the dog, which you can imagine helps us skip some of the unnecessary steps.
Absolutely.
Okay, that's really cool.
I had no idea about this.
Yeah, yeah.
It's a cool, I'd say like moderate level feature.
Like I think if you're just starting to use Pydantic, you probably haven't touched discriminated unions much.
But we hope that it's simple enough to implement that most folks can use it if they're using unions.
Yeah, that's cool.
I don't use unions very often, which is probably why.
Other than, you know, something pipe none, which is, you know, like optional.
But yeah.
Yeah.
If I did, I'll definitely remember this.
Yeah.
Alrighty.
So as I've mentioned, this helps for more efficient validation.
And then where this really comes and has a lot of value is when you are dealing with lots of nested models or models that have tons of fields.
So let's say you have a union with like 10 members and each member of the union has 100 fields.
If you can just do validation against 100 fields instead of 1,000, that would be great in terms of a performance gain.
And then once again, with nested models, you know, if you can skip lots of those union member validations, also going to boost your performance.
Yeah, for sure.
You know, an example where this seems very likely would be using it with Beanie or some other document database where the modeling structure is very hierarchical.
You end up with a lot of nested sub-identic models in there.
Yeah.
Yeah, very much so.
Cool.
So as a little bit of an added benefit, we can talk about kind of this improved error handling, which is a great way to kind of visualize why the discriminated union pattern is more efficient.
So right now we're looking at an example of validation against a model that doesn't use a discriminated union.
And the errors are not very nice to look at.
You basically see the errors for every single permutation of the different values.
And we're using nested models.
So it's very hard to interpret.
So we don't have to look at this for too long.
It's not very nice.
But if we look at...
But basically the error message says, look, there's something wrong with the union.
If it was a string, it is missing these things.
If it was this kind of thing, it misses those things.
Like if it was a dog, it misses this.
If it's a pet, a cat, it misses that.
Right.
It doesn't specifically tell you.
Exactly.
It's a dog.
So it's missing like the collar size or whatever, right?
Right.
Exactly.
But then, and I'll go back and kind of explain the discriminated model for this case in a second.
But if you look at this is the model with the discriminated union instead, we have one very nice error that says, okay, you're trying to, you know, validate this X field and it's the wrong type.
Right.
So, yeah, the first example that we were looking at was using string type discriminators.
So we just had this pet type thing that said, oh, this is a cat or this is a dog, that sort of thing.
We also offer some more customization in terms of we also allow callable discriminators.
So in this case, this field can be either a string or this instance of discriminated model.
So it's kind of a recursive pattern, right?
And that's where you can imagine the nested structures becoming very complex very easily.
And we use this kind of callable to differentiate between, you know, which model we should validate against.
And then we tag each of the cases.
So a little bit more of a complex application here.
But once again, when you kind of see the benefit in terms of errors and interpreting things and performance, I think it's generally a worthwhile investment.
That's cool.
So if you wanted something like a composite key equivalent of a discriminator, right?
Like if it has this field and its nested model is of this type, it's one thing versus another.
Like a free user versus a paying user.
You might have to look and see their total lifetime value plus that they're a registered user.
I don't know.
Something like you could write code that would pull that information out and then discriminate which thing to validate against, right?
Yeah, exactly.
Yeah, it definitely comes in handy when you have like, you're like, okay, well, I still want the performance benefits of a discriminated union.
But I kind of have three fields on each model that are indicative of which one I should validate against, right?
Yeah.
And it's like, well, you know, taking the time to look at those three fields over the hundred is definitely worth it.
Just a little bit of complexity for the developer.
Yeah, cool.
One other note here is that discriminated unions are-
Can we go back really quick on the previous one?
So I got a quick question.
So for this, you write a function.
It's given the value that comes in, which could be a string.
It could be a dictionary, et cetera.
Could you do a little bit further performance improvements and add like a functools, LRU cache to cache the output?
So every time it sees the same thing, if there's repeated data through your validation, it goes, I already know what it is.
What do you think?
Yeah.
I do think that would be possible.
That's definitely an optimization we should try out and put in our docs for like the advanced, advanced performance tips.
Yeah.
Because if you've got a thousand strings and then, you know, that were like, it's maybe male, female, male, female, male, male, female, like that kind of where the data is repeated a bunch.
Yeah.
Then it could just go, yep, we already know that answer.
Yeah.
Potentially.
I don't know.
Yeah.
No, definitely.
And I will say, I don't know if it takes effect.
I don't think it takes effect with discriminated unions because this logic is kind of in Python.
But I will say we recently added a like string caching setting because we have kind of our own JSON parsing logic that we use in Pydantic Core.
And so we added a string caching setting so that you don't have to rebuild the exact same strings every time.
So that's a nice performance piece.
Yeah, nice.
Caching's awesome.
Until it's not.
Yeah, exactly.
So one quick note here is just that discriminated unions are still JSON schema compatible, which is awesome for the case where you're once again defining like API request and responses.
You want to still have valid JSON schema coming out of your models.
Yeah, very cool.
And that might show up in things like open API documentation and stuff like that, right?
Yep, exactly.
So I'll kind of skip over this.
We already touched on the callable discriminators.
And then I'll leave these slides up here as a reference.
Again, I don't think this is worth touching in too much detail.
But just kind of another comment about if you've got nested models, that still works well with discriminated unions.
So we're still on the pet example, but let's say this time you have a white cat and a black cat model.
And then you also have your existing dog model.
You can still create a union of, you know, your cat union is a union of black cat and white cat.
And then you can union that with the dogs and it still works.
And once again, you can kind of imagine the exponential blow up that would occur if you didn't use some sort of discriminator here in terms of errors.
Yeah. Very interesting.
Okay, cool.
Yeah.
So that's kind of all in terms of my recommendations for discriminated union application.
I would encourage folks who are interested in this to check out our documentation.
It's pretty thorough in that regard.
And I think we also have those links attached to the podcast.
Yeah, definitely.
And then performance improvements in the pipeline.
Is this something that we can control from the outside?
Is this something that you all are just adding for us?
Yeah.
Good question.
This is hopefully maybe not all in the next version, but just kind of things we're keeping our eyes on in terms of requested performance improvements and ideas that we have.
I'll go a little bit out of order here.
We've been talking a bunch about core schema and kind of maybe deferring the build of that or just trying to optimize that.
And that actually happens in Python.
So one of the biggest things that we're trying to do is effectively speed up the core schema building process so that import times are faster and just Pydantic is more performant in general.
Well, so one thing that I'd like to ask about, kind of back on the Python side a little bit, suppose I've got some really large document, right?
Really nested document.
Maybe I've converted some terrible XML thing into JSON or I don't know, something.
And there's a little bit of structured schema that I care about.
And then there's a whole bunch of other stuff that I could potentially create nested models to go to, but I don't really care about validating them.
It's just whatever it is, it is.
Yep.
What if you just said that was a dictionary?
Would that short circuit a whole bunch of validation and stuff that would make it faster potentially?
Yeah.
Can it turn off the validation for a subset of the model if it's really big and deep and you don't really care for that part?
Yeah, good question.
So we offer an annotation called skip validation that you can apply to certain types.
So that's kind of one approach.
I think in the future, it could be nice to offer kind of a config setting so that you can more easily list features that you want to skip validation for instead of applying those on a field-by-field basis.
And then the other thing is, if you only define your model in terms of the fields that you really care about from that very gigantic amount of data,
we will just ignore the extra data that you pass in and pull out the relevant information.
Right.
Okay.
Yeah, good.
Back to the pipeline.
Yeah, back to the pipeline.
So another improvement, we talked a little bit about potential parallelization of things or vectorization.
One thing that I'm excited to learn more about in the future and that we've started working on is this thing called SIMD in Jitter.
And that's our JSON iterable parser library that I was talking about.
And so SIMD stands for single instruction, multiple data.
Basically means that you can do operations faster.
And that's with this kind of vectorization approach.
I certainly don't claim to be an expert in SIMD, but I know that it's improving our validation speeds in the department of JSON parsing.
So that's something that we're hoping to support for a broader set of architectures going forward.
Yeah, that's really cool.
Almost like what Pandas does for Python.
Instead of looping over and validation and doing something to each piece, you just go, this whole column, multiply it by two.
Yep, yep, exactly.
I'm sure not implemented the same, but conceptually the same, just to be clear.
Yep, very much so.
And then the other two things in the pipeline that I'm going to mention are kind of related once again to the avoiding materializing things in Python if we can.
And we're even kind of extending that to avoiding materializing things in Rust if we don't have to.
So the first thing is when we're parsing JSON in Rust, can we just do the validation as we kind of chomp through the JSON instead of like materializing the JSON as a Rust object and then doing all the validation?
It's like, can we just do it in one pass?
Okay, is that almost like generators and iterables rather than loading all into memory at once and then processing it one at a time?
Yeah, exactly.
And it's kind of like, do you build the tree and then walk it three times or do you just do your operations every time you add something to the tree?
Yeah.
And then the last performance improvement in the pipeline that I'll mention is this thing called FastModel.
Has not been released yet.
Hasn't really even been significantly developed.
But this is cool in that it's really approaching that kind of laziness concept again.
So attributes would remain in Rust after validation until they're requested.
So this is kind of along the lines of the defer build logic that we were talking about in terms of like, we're not going to send you the data or perform the necessary operations until they're requested.
Right.
Okay.
Yeah, if you don't ever access the field, then why process all that stuff, right?
And convert it into Python objects.
Yeah, exactly.
But yeah, we're kind of just excited in general to be looking at lots of performance improvements on our end, even after the big V2 speedup.
Still have lots of other things to work on and improve.
Yeah, it sure seems like it.
And if this free-threaded Python thing takes off, who knows?
Maybe there's even more craziness with parallel processing of different branches of the model alongside each other.
Yeah.
So I think this kind of dovetails nicely into like you asked earlier, like, is there a way that we kind of monitor the performance improvements that we're making?
And we're currently using and getting started with two tools that are really helpful.
And I can share some PRs if that's helpful and send links after.
Yeah, sure.
But one of them is Codspeed, which integrates super nicely with CI and GitHub.
And it basically runs tests tagged with this, like, benchmark tag.
And then it'll, you know, run them on main compared to on your branch.
And then you can see, like, oh, this made my code, you know, 30% slower.
Like, maybe let's not merge that right away.
Or conversely, if, you know, there's a 30% improvement on some of your benchmarks, it's really nice to kind of track and see that.
I see.
So it looks like it sets up, this is Codspeed.io, right?
Yeah.
And then it sets up as, say, a GitHub action as part of your CI, CD, and, you know, probably automatically runs when a PR is open and things along those lines, right?
Yep, exactly.
All right.
I've never heard of this.
But, yeah, if it just does the performance testing for yourself automatically, why not, right?
Let it do that.
Yeah.
And then, I guess, another tool that I'll mention while talking about kind of our, you know, continuous optimization is a one word for it, is this tool kind of similarly named called CodeFlash.
So CodeFlash is a new tool that uses LLMs to kind of read your code and then develop potentially more performant versions, kind of analyze those in terms of, you know, is it passed, is this new code passing existing tests?
Is it passing additional tests that we write?
And then another great thing that it does is open PRs for you with those improvements and then explain the improvements.
So I think it's a really pioneering tool in the space, and we're excited to kind of experiment with it more on our PRs and in our repository.
Okay.
I love it.
Just tell me, why is this?
Why did this slow down?
Well, here's why.
Yeah, exactly.
Yeah.
And they offer both, like, local runs of the tool and also built-in CI support.
So those are just kind of two tools that we use to use and are increasingly using to help us kind of check our performance as we continue to develop and really inspire us to, you know, get those green check marks with the, like, performance improved on lots of PRs.
Yeah.
The more you can have it where if it passes the automated build, it's just ready to go and you don't have to worry a little bit and keep testing things and then have uncertainty.
You know that.
That's nice, right?
Yeah.
Because it lets you rest and sleep at night.
Yeah.
Most certainly.
I mean, I said it before, but the number of people who are impacted by Pydantic, I don't know what that number is, but it has to be tremendous.
Because if there's 400,000 projects that use it, like, think of the users of those projects, right?
Like, that multiple has got to be big for, you know, I'm sure there's some really popular ones.
For example, FastAPI.
Yeah.
Right?
Yeah.
And it's just nice to, you know, know that there are other companies and tools out there that can help us to, you know, really boost the performance benefits for all those users, which is great.
All right.
Yeah.
That is really cool.
I think, you know, let's talk about one more performance benefit for people and not so much in how fast your code runs, but in how fast you go from raw data to Pydantic models.
So, one thing, you probably have seen, we may have even spoken about this before.
Are you familiar with JSON to Pydantic, the website?
Yeah, it's a really cool tool.
Yeah.
It's such a cool tool.
And if you've got some really complicated data, like, let's see, I'll pull up some weather data that's in JSON format or something, right?
Like, if you just take this and you throw it in here, just don't even have to pretty print it.
It'll just go, okay, well, it looks like what we've got is, you know, this really complicated nested model here.
And it took, you know, we did this while I was talking, it took 10 seconds from me clicking the API to get a response to having, like, a pretty decent representation here.
Yeah.
It's great in terms of, like, developer agility, especially, right?
It's like, oh, I've, you know, heard of this tool called Pydantic.
I've seen it in places.
Like, I don't really know if I want to manually go build all these models for my super complicated JSON data.
It's like, boom, three seconds, done for you, basically.
Exactly.
Like, is it really worth it?
Because I don't want to have to figure this thing out and figure out all the types.
And like, no, just paste it in there and see what you get.
You're going to be, it won't be perfect, right?
Some things, if they're null in your data, but they could be something that would make them an optional element.
Like, they could be an integer or they could be null.
It won't know that it's going to be an integer, right?
Right.
So you kind of got to patch it up a tiny bit.
But in general, I think this is really good.
And then also, you know, just drop in with your favorite LLM, you know.
I've been using LLM Studio, which is awesome.
Nice.
I heard you talk about that on one of the most recent podcasts, right?
Yeah, yeah.
It's super cool.
You can just download Llama 3 and run it locally with like a, I think my computer can only handle 7 billion parameter models.
But you know that you get pretty good answers.
And if you give it a piece of JSON data and you say, convert that to Pydantic, you'll get really good results.
You have a little more control over than what you just get with this tool.
But I think those two things, while not about runtime performance, you know, going from I have data till I'm working with Pydantic, that's pretty awesome.
Yeah, definitely.
And if any, you know, passionate open source contributors are listening and want to create like a CLI tool for doing this locally, I'm sure that would be very much appreciated.
I think this is based on something that I don't use, but I think it's based on this data model code generator, which I think might be a CLI tool or a library.
Let's see.
Yes.
Oh, yeah.
Very nice.
But here's the problem that, you know, you go and define like a YAML file.
Like it's, it's just not as easy as like there's a text field.
I paste in my stuff, but it does technically, technically work, I suppose.
Yeah.
But no, definitely the LLM approach or just the basic website approaches are very quick, which is nice.
Yeah.
And speaking of LLMs, just really quick, like I feel, you know, you get some of the Python newsletters and other places like, here's the cool new packages.
A lot of them are like nine out of 10 of them are about LLMs these days.
I was like, that feels a little over the top to me.
But I know there's other things going on in the world.
But, you know, just put your thoughts on LLMs and coding these days.
I know you write a lot of code and think about it a lot and probably use LLMs somewhere in there.
Yeah.
No, for sure.
I'm pretty optimistic and excited about it.
I think there's a lot of good that can be done and a lot of productivity boosting to be had from integrating with these tools, both in your like local development environment and also just in general.
I think sometimes, you know, it's also great in the performance department, right?
Like we can see with CodeFlash, using LLMs to help you write more performant code can also be really useful.
And it's been exciting to see some libraries really leverage Pydantic as well in that space in terms of like validating LLM outputs or even using LLM calls in Pydantic validators to validate, you know, data along constraints that are more like language model friendly.
So, yeah, I'm optimistic about it.
I still have a lot to learn, but it's cool to see the variety of applications and kind of where you can plug in Pydantic in that process for fun.
Yeah, I totally agree.
Right now, the context window, like how much you can give it as information then to start asking questions is still a little bit small.
Like you can't give it some huge program and say, you know, find me the bugs where this function is called or, you know, whatever.
And it's like, it doesn't quite understand enough all at once, but that thing keeps growing.
So eventually someday we'll all see.
Yep.
All right.
Well, let's talk just for a minute, maybe real quick about what you all are doing at Pydantic, the company, rather than Pydantic, the open source library.
Like what do you all got going on there?
Yeah, sure.
Sure.
So Pydantic has, the company has released our first commercial tool.
It's called Logfire and it's in open beta.
So it's an observability platform.
And we would really encourage anyone interested to try it out.
It's super easy to get started with, you know, just the basic like pip install of the SDK and then start using it in your code base.
And then we have the kind of Logfire dashboard where you're going to see the observability and results.
And so we kind of adopt this like needle in the haystack philosophy where we want this to be a very easy to use observability platform that offers very like Python centric insights.
And it's this kind of opinionated wrapper around open telemetry, if folks are familiar with that.
But in kind of the context of performance, one of the great things about this tool is that it offers this like nested logging and profiling structure for code.
And so it can be really helpful in kind of looking at your code and being like, we don't know where this, you know, performance slowdown is occurring.
But if we integrate with Logfire, we can see that like very easily in the dashboard.
Yeah, you have some interesting approaches like specifically targeting popular frameworks like instrument FastAPI or something like that, right?
Yeah, definitely.
Trying to kind of build integrations that work very well with FastAPI, other tools like that.
And even also offering kind of like custom features in the dashboard, right?
Like if you're looking at, you know, if you're using an observability tool, you're probably advanced enough to want to add some extra things to your dashboard.
And we're working on supporting that with fast UI, which I know you've chatted with Samuel about as well.
Yeah, absolutely.
I got a chance to talk to Samuel about Logfire and some of the behind the scenes infrastructure was really interesting.
But also speaking of fast UI, you know, I did speak to him.
When was that?
Back in February.
So this is a really popular project.
And even on the, I was like, quite a few people decided that they were interested in even watching the video on that one, which, yeah.
Anything with fast UI?
Sorry, did you say anything with fast UI?
Yeah.
Are you doing anything on the fast UI side or are you on the Pydantic side of things?
Yeah, good question.
I've been working mostly on Pydantic, just, you know, larger user base, more feature requests.
But excited to, I've done a little bit on the fast UI side and excited to kind of brush up on my TypeScript and build that out as a more robust and supported tool.
I think, especially as we grow as a company and have more open source support in general, that'll be a priority for us, which is exciting.
Yeah.
It's an interesting project.
Basically, a cool way to do JavaScript front ends and React and then plug those back into Python APIs, like FastAPI and those types of things, right?
So, yeah.
Yeah.
And kind of a similarity with fast UI and Logfire, the new tool, is that there's pretty seamless integration with Pydantic, which is definitely going to be one of the kind of core tenets of any products or open source things that we're producing in the future.
Yeah, I can imagine that's something you want to pay special attention to is like, how well do these things fit together as a whole rather than just, here's something interesting, here's something interesting.
Yeah.
Awesome.
All right.
Well, I think that pretty much wraps it up for the time that we have to talk today.
Let's close it out for us with maybe final call to action for people who are already using Pydantic and they want it to go faster or maybe they could adopt some of these tips.
What do you tell them?
Yeah.
I would say, you know, inform yourself just a little bit about kind of the Pydantic architecture just in terms of like, what is core schema and why are we using Rust for validation and serialization?
And then that can kind of take you to the next steps of when do I want to build my core schemas based on kind of the nature of my application?
Is it okay if imports take a little bit longer or do I want to delay that?
And then take a look at discriminated unions.
And then maybe if you're really interested in improving performance across your application that supports Pydantic and other things, trying out Logfire and just seeing what sort of benefits you can get there.
Yeah.
See where you're spending your time is one of the very, you know, not just focused on Pydantic, but in general, our intuition is often pretty bad for where is your code slow and where is it not slow?
You're like, that looks really complicated.
That must be slow.
Like, nope.
It's that one call to like some sub module that you didn't realize was terrible.
Yeah.
And I guess that kind of circles back to the like LLM tools and, you know, integrated performance analysis with Codspeed and CodeFlash and even just other LLM tools, which is like, use the tools you have at hand.
And yeah, sometimes they're better at performance improvements than you might be, or it can at least give you good tips that give you, you know, a launching point, which is great.
Yeah, for sure.
Or even good old C profile built right in, right?
If you really, if you want to do it that way.
Awesome.
Yeah.
All right.
Well, Sydney, thank you for being back on the show and sharing all these tips and congratulations on all the work you and the team are doing.
You know, what a success Pydantic is.
Yeah.
Thank you so much for having me.
It was wonderful to get to have this discussion with you and excited that I got to meet you in person at PyCon recently.
Yeah, that was really great.
Really great.
Until next PyCon.
See you later.
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.
Code comments and original podcast from Red Hat.
This podcast covers stories from technologists who've been through tough tech transitions and share how their teams survived the journey.
Podcasts are available everywhere you listen to your podcasts and at talkpython.fm/code dash comments.
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.