Loading video player...
Hey there. How's it going everybody? In
this video, we're going to be learning
how to use Pyantic. Pyantic is Python's
most popular data validation library and
it uses Typants to do this validation.
So, if you're already using Typants in
your code, then this should feel pretty
natural to add to our projects.
Basically, Pyantic's primary purpose is
to ensure that the data coming into our
application meets certain expectations.
So, if you've ever struggled with data
coming into your applications not being
the right type or format, or if you've
written a lot of manual validation code
that's ugly and hard to maintain, then
Pyantic is going to make all of this a
lot easier for us. And Pyanic is going
to be important for us to learn because
it's being used in so many different
libraries lately. So, for example, fast
API uses this for validating API
requests and responses. It's used in
data processing to ensure clean data.
And if you've worked with any AI tools,
there's also Pantic AI and that allows
us to build AI agents with structured
and validated outputs. So really
anywhere that you need to make sure that
your data is what you expect it to be.
Uh Pyantic is going to be our go-to.
Now, since Pidantic uses Python type
hints for validation, if you're totally
new to types or static type checking in
Python, I'd recommend watching my typing
and type checking video first and then
coming back to this one. I'll have that
linked in the description section below
if anyone is interested. But with that
said, let's go ahead and get started.
So, first, let's take a look at why
Pyantic is so useful. We can do our own
checks on our data manually. So why
would we use something like paidantic
instead of writing this from scratch? So
I have a quick example here of what some
manual validation might look like. So we
have this create user function that
takes a username, email, and age. And
look at all the validation code that
we're writing in here so far. Uh so
we're checking if the username is a
string. We're checking if the email is a
string and if the age is an integer. So
let me run this real quick and we can
see how this works. So I'll run this.
Let me make this output a little larger
here. So first let me look at uh both of
the test users that I created here. So
we have user one create user. We pass it
in a username as a string, an email as a
string, and an age as an integer. For
this second user, I passed in uh the
username as a string, the email as none,
and the age as a string. So we can see
that it creates that first user
successfully and printed that out. But
when we try to create the second user
with the invalid data, we get an error.
Now it fails on the first validation
error that it hits. So even though we
have multiple problems with this data,
uh the email is none and the age is a
string. We have multiple problems there.
We only find out about one error at a
time. Right now it's reporting that the
email is invalid. And if I was to go
back and fix that invalid email error
that it tells us about, then we'd hit
that next problem that it didn't tell us
about uh as soon as I rerun this. So
going back and fixing those things one
at a time isn't really a great user
experience. Also, this is a simple
example with just three fields that
we're validating. Uh and look how much
code that we have just for that small
bit of validation. Imagine if we had a
complex object with nested data, lists,
uh, dates, and other types. That
validation code would get extremely
overwhelming and super hard to maintain
very quickly. So, let me show you what
this same validation would look like
with paidantic. So, I'm going to uh just
delete the manual validation that we had
before. Actually, let me just comment
that out. And now, let me uncomment it
the uh pyantic code here. and save that.
Okay, so this is some very simple
pyantic code. Now, if you've ever used
data classes, uh you can see that
pyantic gives us syntax that looks very
similar to a data class. But data
classes don't actually validate anything
at runtime. So, Pyantic gives us both
the benefits of a nice syntax uh with
type hints and it actually validates
your data at runtime. And Pyantic will
collect all of those validation errors
at once. So we don't just get the first
error like we saw in our manual
validation example. So now instead of
all of that manual validation, we have a
class here that inherits from base model
and then we just define our fields with
type hints and that's it. Uh we can
create the user. Uh pidantic
automatically validates everything for
us and then we are printing that out.
So, let me go ahead and run this simple
example here. And just like before, uh
I'm using those same users that I had
before where the first one is good and
the second one is bad. And now we can
see here that the first user prints out
successfully. On the second user, we can
see that it's saying that we have two
validation errors for the user. Uh one
is the email, it should be a valid
string. and uh the age it should be a
valid integer and it also gives us a
little bit of extra information there.
So now let me uh minimize that a little
bit. So now we're getting both of those
validation errors at once and we didn't
have to write uh any of the validation
code ourselves. So that's just the bare
minimum of what we can do with Pyantic.
Uh there's so much more to cover, but
before we get started, I wanted to show
you what this looked like uh compared to
trying to manually validating this stuff
ourselves. Uh so now that we've seen
that quick intro, let's start from
scratch and see how we get this
installed and then look at some more
complex examples. So uh I can go ahead
and just delete everything that I have
here so far. And now if we were to
install this, I'm just going to bring up
my terminal here. Uh, installing this is
pretty straightforward. If you're using
pip, then you could say uh pip install
pyantic. Um, now I'm going to be using
uh UV for this tutorial. Uh, if you
haven't watched my uh UV tutorial, I
have a video on that as well, but that
will just be uh UV add pyantic. Now,
I've already got this installed, so it
shouldn't really need to uh install
anything else there. So, I'll clear that
out. Now, one thing that I do want to
point out is that we're going to be
using Pyantic version two in this
tutorial. And that's important because
the transition from version one to
version two was pretty significant. Uh,
a lot of the method names changed. Uh,
so for example, in version one, you
would use uh DICT to convert a model to
a dictionary, but in version two, that's
now model dump. And in version one,
you'd use JSON to convert to JSON, but
now it's model dump JSON. And there's
several others as well. So if you're
following along with older tutorials uh
or documentation, you might see some of
those old method names. Uh and it's good
to know which version uh they are using
and which version you're using just so
you don't get confused there. So let me
go ahead and verify really quick uh the
version that I have installed. If I just
open up the Python interpreter here, uh,
and then I import paidantic, then I can
check out the version just by saying,
uh, pyantic and checking out double
version
and run that. We can see that we are on
2.12.3.
But as long as you are using uh, version
two, then you should be able to follow
along just fine with this tutorial. Uh,
so go ahead and clear that out. Okay, so
now that we have Pyantic installed and
we verified that we're on version two,
we can actually start diving into how to
actually use this. And we already saw a
quick example at the beginning, but now
let's create our first proper pidantic
model from scratch and really understand
what's going on. So we already removed
all the code that we had before. Um, so
now let's just import what we need to
import and that's going to be the base
model. So I'll say from paidantic import
and this is going to be base model and
I'm also going to import uh validation
error because we are going to use that
in a bit as well. And while I'm
importing some stuff here I'm also going
to be using uh some datetimes for some
of our fields later on. Uh so let me go
ahead and import datetime as well. So
I'll say from datetime import uh
datetime. Okay. And don't worry about
those rearranging there. I just uh it's
how my editor is set up to where it auto
sorts my imports. Okay. So now let's
create our user model. To create a
pyantic model, we create a class that
inherits from base model. So let's
create a user class. So we'll say class
user and this is going to inherit from
base model. And now we define our fields
using type annotations. So let's start
simple with just a couple of required
fields. So let's add a username here and
that'll be a string. And let's also add
an email and that will be a string as
well. And that's really all there is to
it for a basic model. These are required
fields because they don't have default
values. Pyantic will require these to be
passed in when we create a user
instance. Uh, and these type hints
aren't just documentation like they
would be with a regular class or data
class. Pyantic actually uses these to
validate the data at runtime. So let me
go ahead and create a user and show how
this works. So we'll say user is equal
to and let's just leave that empty for
now and we will print out that user. So
if I just create an empty user there,
then this won't be valid because like I
said, if we don't have any default value
for the username or email, then they're
going to be treated as a required field.
So if I run this now, then we can see
here that this is saying that we have
two validation errors for the user and
that the username field is required and
the email field is required. So now let
me go ahead and fill those in with some
um fields here. So I'll just uh use
strings for both of those so that it's a
valid user. So for the username I'll
just say coreyms and then for the email
I will say email is equal to uh corem
shaergmail.com.
Okay. So if I save that and run it now
uh then we can see that that works. So
now let me add in a few more fields to
show the different types of fields that
we can have. So right now username and
email are required fields but we can
also have optional fields with defaults.
So let me add a bio and an is active
field with default values. So up here in
our user class, I will go ahead and add
a bio. And the bio is going to be a
string. And we are going to set a
default value here of just an empty
string for the bio. And now we'll also
have is active. And let's make this a
boolean. And I'll set the default of
that equal to true. So now since these
have default values, then these are
going to be optional fields. So, if I
rerun this without adding any additional
information to our user here that we're
creating. If I save this and run it,
then we can see that that still works
and it just used those default values
here of bio equal to an empty string and
is active is equal to true. Okay. But
what if we want a truly optional value
that isn't required and is none by
default? So, for example, let's say that
I have an optional full name field. uh
it wouldn't make any sense to give
someone a default full name. So, we'd
want to set this to none by default. So,
if I come up here and say full name and
I say that this is going to be a string
and set this equal to none. Now, this
might look like what we want, but let's
think about what we're doing here. We're
immediately saying that our full name is
going to be a string. uh but that its
default value is none but none is not a
string so that doesn't make any sense.
So to do this we can use the union
syntax to say that our full name can
either be a string and then use that
pipe character for the union syntax and
say string or none and then the default
is none. So you can see that it had that
underlined there for a second because I
have my type checker on. If you have
your type checker on uh and do it this
way, then it should stop yelling at you
for that. Okay, so now let me add a few
more fields that we uh might expect when
creating a user. So we might have a
unique ID for each user and I'll just
set that to a required field that's an
integer for now. So above our username
here, I will say UID and I'm going to uh
set that as an integer. Now sometimes in
Python, you'll see people use ID as the
field name. I don't like using ID
because ID is a uh built-in keyword in
Python. So I like to use something like
UID instead. Okay. So what else might we
want for a user? Uh, so we might want to
track when a user was verified, but that
might not happen right away. So I will
have a field here called verified
and that will be a datetime. And since
this can be none by default, then I'll
use a union there of none. Set that as
none for the default. Okay. So now we
have a mix of required fields, optional
fields with defaults and optional fields
that can also be none. So let me update
our user here to include that required
uh UID.
And let's just set this equal to uh I
think I said that that was going to be
an integer. Yep. So I'll just set that
equal to 1 2 3. And let me go ahead and
run this to make sure it still works.
Okay, so that still works fine. Uh so
now let's talk about accessing and
modifying data. We can access fields
using dot notation just like any other
Python object. So so far we've just been
printing out the entire user here. Uh
but if I wanted to grab a specific
field, then I could use that with the
dot notation. So for example, if I
wanted the username, then I could say
user username. And if I save that and
run it, then we can see that it's giving
us that username back. Now, we can also
use this uh to set specific fields. Now,
one thing to be aware of is that model
instances are mutable by default and
also by default they don't revalidate uh
when you change a field. So, for
example, if I come down here and say
user.bio
is equal to 1 2 3. Now, if you remember,
our bio was set to a string up here. Uh,
so if I print out that bio and run that,
then you can see that when we reassign
this, it ran just fine. So, it's not
revalidating on assignment, and that's
the default. Now, you can change that
behavior in the model configuration, and
we'll look at that later in the
tutorial, but just be aware that this is
the default. uh changing fields after
creation doesn't trigger revalidation.
So let me go ahead and set that to a
proper string now the way it should.
I'll just say Python developer for our
bio. Save that and run it. And we can
see that that works fine. So now let me
show you a couple of useful methods for
working with pyantic models. If we want
to convert our model to a dictionary
then we can use the model dump method.
So instead of bio here if we do
model_dump
and that is a method. So we need to put
a parenthesis after that to execute
that. If I save that and run it then we
can see that it gives us a dictionary
representation of our model. Now this is
a Python dictionary. If we wanted JSON
instead then this is going to be
model_dump_json.
And if I save that and run it, then this
is going to be a JSON string now. And
just like when we're working with the
JSON module, we can pass options to
format this nicely. Uh here as well. Uh
so for example, one of the arguments
that this takes is an indent. So I could
say indent is equal to two. If I save
that and run it, then you can see that
now our JSON string is nice and
formatted uh with two spaces there. So
those model dump and model dump JSON
methods are really useful when you need
to serialize your models for storage or
sending over a network. And by serialize
I mean converting your Python object
into a simple format that can be easily
saved to a file or sent to another
system. Okay. So now let's look more
into what happens when validation fails.
Remember at the beginning of the video I
showed that pyantic catches multiple
errors at once. So let me demonstrate
that. Uh I'll create a try accept block
here and try to create a user with bad
data. So let me close down that output
for now. So let me put this inside of a
try accept block. So I will indent that.
And then we will accept and this is
going to be a validation error. We
imported that earlier and I'll just set
that as E. And now we can just print out
that error. Now the fields that we have
in there right now are fine. Uh so
that's not going to throw an exception
there. Let me go ahead and I'll put our
username as none. And I'll set our email
equal to something like one two three.
Save that. And that should go ahead and
throw an error. Now, now you don't need
to use a try accept block here, but I
wanted to show you an example of using
one because usually you'd probably want
to handle these validation errors in
some way. Uh in this example, I'm just
printing out the error. Uh so here I am
passing in actually let's go ahead and
just do all the fields. Uh so let me
also do a UID of 1 123 as a string there
also so I can show you something u
specific about that. So I'm passing in a
string for the UID uh which should be an
integer none for the username uh which
is a required and should be a string and
passing in an integer for an email which
should also be a string. So let me go
ahead and run this. Now, this is a bit
interesting here because we only got two
errors, not three. Uh, it's complaining
about uh the username should be a valid
string and the email being an integer
when it should be a string. But notice
that the UID is not in that error list.
Uh, that's because Pyantic has type
coercion enabled by default. So when we
passed in this string of 1 2 3, it
automatically converted this to an
integer of 1 2 3. So that's actually a
valid conversion and didn't raise an
error. But the email that we passed in
as an integer that was not converted to
a string. Now that might seem weird, but
the reason for that is that it's
extremely common to receive numeric data
as strings, whether that's from JSON,
form inputs, uh URL parameters, or
whatever. It's not as common to expect a
string and get an integer. So, it's
actually nice to have some type
coercions, and that's enabled by
default. Uh but we'll later see in the
video uh how you can make validation
more strict if you want to prevent this
type of coercion from taking place. Uh
for now just know that by default
pyantic will try to convert types when
it makes sense to do so. If it doesn't
make sense then you'll obviously get an
error. So if I change this UID to test
instead of 1 2 3 and rerun this then we
can see that now we're getting three
validation errors because it could not
uh convert that to an integer. Now
actually this would be a lot more uh
clean here. It still get has a trace
back there. That's just because I am
trying to print this out afterwards. Uh
let me comment out that code and rerun
this. And we can see that that is more
clean. Now we can see that we get the
three validation errors and it tells us
exactly uh why each of these failed
validation and even has links uh to
their documentation if you need to see
more about that. Okay. So now that we
understand the basics of creating
models, let's talk about the different
types that we can use and how pi pyantic
validates them. Pyantic supports all the
standard Python types that you'd expect.
uh things like strings, integers,
floats, booleans, uh list, dictionaries,
uh tupil, sets, and also datetime
objects like datetime, date, time, and
time delta, things like that. And we can
use unions uh to say that a field can be
one of several types. And we can use
optionals to allow none values and uh
like we've seen already. And we could
even use literal types to specify exact
values that are allowed. So to
demonstrate some of these, let's create
a new model. And I'll add a blog post
model uh below our user class. So I'm
just going to overwrite everything that
we have so far. And now let's create a
new class. So I will call this blog
post. And we are also going to inherit
from that base model. And now let's just
add uh some things that we might expect
in a blog post. So we're going to have a
title. We're going to have Whoops. Okay.
So a title, that's going to be a string.
We'll have a uh section for content that
will be a string. We'll have view count
that'll be an integer. We'll set the
default of that equal to zero. We'll do
an ispublished boolean here and set this
equal to false by default.
And now let's take a look at a couple of
other types here. So now let's say that
we want a list of tags. So for lists we
can specify what type the items should
be in that list. So the so if we had
something like tags uh then this is
going to be a list of strings. So to
specify that we can say list and then
within the brackets here say str for
strings. Now I'll set a default here and
let me set this default and then I will
explain it afterwards. So I'm going to
set a default field and this is going to
be a default factory and that default
factory is going to be a list. So first
we can see that it's yelling at me here
that field needs to be imported. So I
will go up here and import field and
that's from paidantic that we're
importing that field. Okay. And now we
have this default factory equal to list.
So the default factory is simply a
function that gets called uh to create a
new default value each time you create
an instance. So here every time we
create a user it calls list to create a
fresh empty list for each user. Default
factories are super useful for a lot of
things and we'll look at more
complicated example right after this. Uh
but for now we're just using it to
create an empty list. Now in paidantic
you can actually just use an empty list
as a default uh instead of the default
factory. So I could do something uh like
this just a default value of an empty
list. Um but I'm not going to do that
because I just don't want to teach any
bad habits here. Um, in regular Python
classes, uh, using a mutable default
like an empty list is a problem because
that same list object gets shared across
all instances. Uh, data classes won't
even let you do it and requires you to
use a default factory. Also, so even
though Pyantic handles that safely, uh,
I'm just going to stick with using the
default factory pattern here. Uh if you
want to learn more about why mutable
defaults are a problem in Python, I have
a video on that and I'll leave a link to
that video in the description section
below. All right, so now let me show you
another example of a default factory
with a slightly more complicated default
value. Uh let's say that we wanted a
created at timestamp that defaults to
the current time. Uh now you might think
that you could just do something like
this. So, we'll say that that's going to
be uh datetime and that we could do
datetime dot now and do UTC for the
datetime uh or for the time zone I mean
and I would also need to import UTC from
the datetime library as well. Okay, so
you might think that you'd be able to do
something like this, but this wouldn't
work the way that you'd expect. uh that
would call datetime.now
once when the class is defined not when
each instance is created. So every blog
post would have the same time stamp. Uh
instead we need to use a default factory
with a function that will be executed
each time that we create an instance. So
let me copy this. Uh so just like before
we're going to use a field with that
default factory and then again you might
be thinking that we can just put here uh
datetime.now let's do tz is equal to uh
UTC. Now you might be thinking that
we're good now since we're using that
default factory. But this still isn't
good because we're still executing our
function here. So we need to somehow not
execute our function but also set our
time zone to UTC. Uh because again the
way that we have this now all of our
blog post will have the same timestamp
since it will just run this function
once when we define our class not the
actual timestamp of when we create each
post like we're hoping for. So there are
a couple ways that we can fix this and a
couple ways that we can do this. uh if
you understand lambda functions then we
can use one of these here. So we could
just say that our default factory we
need to pass in a function that is
unexecuted. Right now we are executing
that function. So that unexecuted
function I could just have this be a
lambda and then that lambda will run
datetime.now
uh tz is equal to UTC and lambda is just
an anonymous function. So here we're
saying that every time we create a blog
post, we want the default factory to run
this anonymous lambda function and when
that lambda function is run, we wanted
to calculate the current date time uh
with the time zone of UTC and that will
work uh just fine. You'll see a lot of
people doing it this way. Uh so I wanted
to show you this uh but there's actually
another way to do this uh that some
people might prefer and I'll show you
that also just in case uh you might see
that in some code as well and that other
way is using the funk tools.parial.
So let me import that. So here at the
top uh we will say from funk tools and
we will import partial. And now partial
lets us prefill some arguments to a
function. So it allows us to pass in a
function and some arguments and it
returns a new unexecuted function that
is the original function with those
specific arguments already set. So what
I could say here is I could let me just
cut that out. So I could say that our
default factory is a partial and partial
we want datetime.now
to be the unexecuted original function.
And now we can say what arguments we
want set with that datetime.now
uh function and we want the argument of
tz equal to UTC. Actually that needs to
be all caps. Okay. So what partial is
doing here again is we are uh partial is
going to return an unexecuted uh
function that is the datetime.now
function uh with tz equal to UTC and uh
ready to go. So that'll work also. And
let's add a couple more fields here just
so we can see some more examples. So uh
here's an example of a union type. So
let's say that we have an author ID and
the author ID could either be uh you
know something like user 123 or it could
be an integer uh just like 1 2 3. So we
can use a union type here and we could
just say that this is going to be a
string and then we'll use that pipe
character which is that union and we'll
put int as well. So it can be a string
or an integer. And finally let me also
show you literal types. So these are
great when you have a field that can
only be specific exact values. So let's
say for a status field uh we maybe only
want to allow you know a status of draft
published and archived for our uh blog
post status. But first I'll need to
import literal and that is from uh the
typing module. So we can say from typing
import literal. And now I can create a
new status field here. And this is going
to be literal. And like I said for this
we'll say that it can be a literal of
draft or published if I can spell. And
then let's do one for archived as well.
And we'll set a default here. Uh the
default let's set as draft. So now
status can only be one of those three
exact strings. If you p try to pass in
anything else, then it'll fail
validation. All right. So now let's
create a blog post and show that all
this works. Uh and we're getting to the
point where I'm going to be creating a
lot of objects to show how these things
work. Uh so I don't want to waste your
time by you watching me type all these
out. So I have a snippets file here. Uh
let me go ahead and grab these from my
snippets file and then we'll go over
them. Uh, all of the code that I'm using
in this video, by the way, is going to
be available on GitHub and I'll have a
link to that in the description section
below. But from my snippets file here,
let me go ahead and grab this and then I
will explain it. So, let's test this
out. So, if I look at the blog post
again, we can see that we only have
three required fields here. Uh, so it is
the title, the content, and the author
ID here are the ones that do not have uh
defaults. Now, let me go ahead and put
all of those required fields together
there. Now, sometimes I'll spread these
out based on um, you know, just whenever
I'm typing them uh, to kind of let you
see a little bit better. But let me go
ahead and clean that up and just put
those all together there. So, a blog
post has those three required fields. uh
the title, the content, and the author
ID. And I'm passing all those in. So,
let's uh print this out and see if that
works. Whoops. And I ran that uh without
printing that out. So, let me print out
that post that I created. And now we can
see that that works. Okay. So, we'll
notice a few things here. So, our
default values uh seem to work just
fine. Our tags are an empty list and we
also have a created at uh timestamp that
was generated when we created this
instance. And also you'll notice that uh
since we said that our author ID could
either be a string or an integer since
we passed in an integer as a string it
did not coersse that into an integer and
just kept that as a string. uh which is
fine. If we wanted it to try to coorse
those values like that uh then we could
have explicitly said that we only wanted
integers but we said that strings were
fine. So now that we've seen the basic
types that we can use let's talk about
adding constraints to our fields. So
we've already been using field for
things like default factory. But field
can do a lot more than that. uh we can
use it to add constraints like minimum
and maximum values uh length
requirements and patterns things like
that. Now in Pyanic version two the
recommended way to add these constraints
is using the annotated type from
Python's typing module. So let me add
that to our imports. So up here at the
top uh from the typing module we are
going to import annotated. Okay. Oops.
And I think I spelled that wrong. Yeah,
that is annotated.
And now let's update our user model here
to add some constraints. So for the UID,
instead of saying that this is just an
integer, let's add a constraint that it
has to be greater than zero. So to do
this, I can use an annotated type here.
And within here, I can say that this is
going to be an integer. And now we can
add in our constraints. So to do this, I
can say uh that we want a field that is
GT greater than equal to zero. If you've
never seen this annotated type before,
basically it's a way to add metadata to
our current type. So in this example,
we're saying that we want an integer,
but the metadata included with this
integer is that it is a value greater
than zero. And for username, let's add
some length requirements. So let's say
that we want a username to be between 3
and 20 characters. So here I could say
that I want an annotated type and that's
going to be a string. And now for the
constraints here we can say that uh we
want a field and the we want this to be
the min length equal to three. And then
we'll have a max length here and we'll
set that equal to 20. So let's keep
looking at some other examples here. Uh
so for example uh let's say that we
wanted an age. I can do an age and I'll
do annotated on that as well. Let's say
that we only want people greater than or
equal to the age of 13 uh that can sign
up. So I can say that this is going to
be an integer. Again, we're going to
have this be a field. And let's do this
was greater than zero. I want this to be
greater than or equal to. So that'll be
G. So greater than or equal to 13 and
let's do less than or equal to. We'll
set that to I don't know 130 years old
or something like that. So you can see
the pattern here. We use GT for greater
than uh GE for greater than equal to. It
would be LT for less than and LE for
less than or equal to. And for strings
we have min length and max length. Uh
now let's update our blog post model to
add some constraints there as well. Uh
so for the title, let's say that this is
going to be annotated and it will be a
string and we'll have this be a field
with a min length of one and we'll set a
max length on our title. Uh we'll have
that be 200 characters. For our content,
I'm just going to go ahead and copy this
so I don't have to type it out again.
for our content. Uh let's just have this
be a minimum length. We'll say that the
minimum length uh for the content has to
be at least 10 characters. Now, we could
keep adding constraints for a lot of
these, but I think you all get the idea.
Uh but let me bring uh let me add one
more field here uh to show you how
pattern matching with regular
expressions. So, for our blog post here,
let me add a slug. and the slug. Let's
have this be uh annotated as well. And
this is going to be a string. Now, a
slug field for a URL, you usually want
some URL friendly identifiers in here.
So, let's say that a slug should only
contain lowercase letters, numbers, and
hyphens. To do this, I can say that the
field is going to have a pattern. And
for this pattern, we can set this uh to
a regular expression. So I will have
this be I don't know how familiar you
all are with regular expressions, but
basically this part here is the
beginning. This part here is the end. Uh
and in between these we will have all
lowercase letters uh all digits. So 0 to
9 and we will also have hyphens. And
this plus here is just saying that we
can have one or more of those. If you've
never used regular expressions before,
then I do have uh a separate video
specifically on those. Uh I'll leave a
link to that video in the description
section below as well if you're in more
interested in learning how these work
explicitly, but I just wanted to quickly
show that here uh to show that we can
pretty easily add these to our
validation as well. So now the slug has
to match that regular expression pattern
uh or the validation will fail. All
right. So now let's test these
constraints and see what happens when we
try to create a user that violates them.
So I'm going to comment out the blog
post uh here before uh where we were
printing this out. And so let me comment
that. And now let's create a user. And
again I'm going to grab this from my
snippets. So, from my snippets file
here, uh, I'm going to grab an invalid
user and paste this in. So, here we have
a UID set to zero, which violates our
greater than zero constraint. We have a
username that's only two characters,
which is too short. And we have an age
of 12, which is below our minimum of 13.
So, let me save this and run it. And
let's see what we get here. And we can
see that we're getting all three of
these validation errors. It's telling us
that the UID needs to be greater than
zero. Uh the username should have at
least three characters and that the age
should be greater than or equal to 13.
So Panic caught all those uh constraint
violations. But not only that, but it
gave us a clear error message of exactly
what's wrong. So that's how you add
constraints using field and annotated.
Now this pattern is really flexible and
you can combine multiple constraints on
the same field and this is the more
modern uh pyantic version two uh way of
doing things. If you're looking at older
code or tutorials you might see things
like con stir or con int and I think
that stood for constrained integer
constrained uh string things like that.
Those are now deprecated in favor of
this annotated pattern that we're using
here. Now, in addition to adding
constraints manually with field like
we've done here, Pideantic also provides
a bunch of specialized types that uh
have constraints built in. And these are
really convenient for common uh
validation scenarios. Let me pull up the
documentation here really quick to show
you what all is available. And I'll have
links to these in the description
section below as well. But if I look at
these pidantic types, uh we can see here
like there's a shorthand for positive
int uh negative int. Uh let's scroll
through. We can see all these on the
right side here as well. Um but we have
if I go down further, we have non-
negative float, strict floats, things
like that. Um here we have UIDs, so
unique identifiers. You can see it even
has things like file path and things
like that as well. So a lot of these uh
are built in here. Now a lot of these
are just shorthands for the annotated
field uh with um that we saw before. So
for example if I look at this positive
integer here we can see that this is
basically uh just a annotated integer
with greater than zero. That's what we
saw before and there are a bunch of
other shortorthhands like this. Uh so
these save you from having to write all
these out yourself. Um they also break
these out into network types as well. So
let me open this page also. We can see
that we have network types here. And
these contain a few others that we'll be
using also. So some of the most useful
ones that we'll be using from these two
pages are things like email string for
validating email addresses. Um HTTP URL
for validating URLs. um the UID for
unique identifiers and secret string for
sensitive data like passwords and I can
see that it's on this networking page is
where they have the uh HTTP URL and also
the email string down here as well. Now
some of these require extra
dependencies. So for example, email
string requires the email validator
library and when you install pyantic by
default you just get the core library.
Uh to get these extras you need to
install them uh explicitly. So let me
show you how to do that. So I'm going to
install the email extra so that we can
use the email string. So here in my
terminal uh we can just say uv add and
this is going to be paidantic and then
in brackets we will say email. So if I
run this then it should install all of
that. Okay. And that would be the same
with a pip install. So those brackets
when installing uh packages with pip or
UV are optional dependencies that can be
installed with the package. You might
see that from time to time and that's
what that is. All right. So now that we
have that installed, let's update our
user model to use some of the special
types. So first let me import uh what we
need. So up here at the top let's add to
our pyantic imports here. Um so I will
import email str and also let's do http
url and also let's do uh secret string
uh secret str and also I'm going to use
uh uids for the unique identifiers. So
that's actually going to come from the
uid module. So I'll say from uyu id
import import uyu id and let's also
import uyu ID4 is the one that we are
going to use. So now let's update our
user model. So instead of UID being a
constrained integer here, let's make it
a UID with a default factory that
generates a new unique ID for each user.
So instead of this annotated here, I'm
going to say that this is a UYU ID. And
let's go ahead and have a default
factory. And I will use that UU ID4
function that we imported. Uh we do not
want to put the parenthesis because we
don't want to execute that. We want the
unexecuted function there as our default
factory. And what that'll do is it'll
generate a new unique ID for each user
when we create one. Okay. So let's also
change the email from a regular string
to an email string. So here for email,
I'm going to use that email stred.
And I find that one super useful because
we could try to come up with our own
constraints or regular expressions to
check for valid emails. But this just
allows us to easily specify that we want
an email string and paidantic does all
that background work for us. So we don't
have to worry about it which is super
nice. And let's add a couple of other
new fields here as well. Uh let's say
that a user can have a website and for
that we can use that HTTP URL that we uh
had before. Now this will be optional.
So I'll say that this can be an HTTP URL
or none. set that equal to none by
default. And let's also add a password
field uh using that uh secret string
that we imported. Uh so I can say
password and that is just going to be a
secret. Let me spell that correctly.
Secret str. So the secret string type
here is great for sensitive data because
it will hide the password and logs and
things like that when you print out the
model. All right. So now let's test
these special types. So let me remove
that try except block that we had uh
before and create a valid user here. Now
I actually think I have this in my
snippets file as well. Yeah, I do. Okay.
So I have a valid user here. Let me grab
this and I will just overwrite that try
accept block there. Okay. So notice
we're not passing in a UID anymore
because that'll now be autogenerated and
we're now passing in a password since it
is required. Now I am getting a warning
here. I think that is probably a rough
warning uh that I have set up that just
says that I'm probably hard- coding a
password into my code. Uh that's fine.
So I'll ignore that for now. So if I run
this and it's printing out this user
then we can see that the UID was
generated for us and also look at the
password here. It's showing as a secret
string with these asterisk instead of
the actual password. And if we dump the
model to a dictionary or JSON uh the
password will be hidden there too. But
if we need to access the actual value
then we can actually do that. All we'd
have to do is say user.p
password and then on that password we
can use this get secret value method
here. So if I save that and run it then
you can see with that get secret value
we actually do uh get that printed out.
So it blurs out that password for
security but if you need to grab it then
it's easy to get that. So these special
pyanic types are super convenient. Uh
they give you complex validation with
just a simple type annotation. Uh email
string validates the email format. HTTP
URL validates URLs and secret string
protects sensitive data. And there are
tons more in that documentation if you
need them. So I'll be sure to leave uh
links to these pages in the description
section below because there's a lot of
useful ones in there. So, now that we've
seen how to use built-in constraints and
special types, now let's talk about
creating our own custom validators.
Sometimes the built-in validation isn't
enough, and you need to add in your own
logic. Pyantic makes this really easy
with decorators. So, let me import what
we need for custom validators. Um, I'll
add these to our Pyantic import. So
right here at the end of our pyanic
import, uh the custom validators I'm
going to be taking a look at here. It's
going to be field validator and we're
also going to import model validator.
And we are also going to import this
validation info as well. And once I save
that, my code's going to auto format
here and put these on different lines
since that's now getting a little long.
Now, one thing to mention before we dive
into these is that in Pyanic version
two, we use field validator and model
validator. In version one, there was
just a validator decorator. So, if
you're looking at older tutorials or
documentation, you might see the old
validator decorator being used, but
we're using the new latest decorators
here. Okay. So, let's add a custom
validator to our user model. So let's
say that we want to validate that
usernames only contain alpha numeric
characters and underscores and we want
to normalize them to lowercase. I'll add
this method inside the user class. And
I'm also going to grab this from my
snippets as well and then we'll go over
it uh really quick. Let me go ahead and
clean up our user model here because I
have all these on different lines so
far. Okay. Now, this is going to be in
my snippets as well. Let me grab these.
Okay. So, I'll grab this and I will just
put this after the fields in our user
model and indent that. Okay. So, let me
explain what's happening here. So, we
use the field validator decorator and
pass in the name of the field that we
are going to be validating and that's
going to be username. So this needs to
be a class method also. So we add that
decorator as well. We're calling this
validate username. Since it's a class
method, it takes the class first and
then we are receiving this value v as a
string and then we do our validation
logic. So our validation logic here we
are just uh saying okay we're going to
get rid of underscores here and then
check if that is an alpha numeric value
and if it's not if that doesn't equal uh
true then we're just going to raise a
value error here that says our username
must be alpha numeric uh underscores are
allowed. Um, if everything goes well,
then we're just going to return that
alpha numeric value with underscores uh
all lowercase. Really, the takeaway here
for this specific pyantic video, I don't
want you to get too bogged down in uh
the exact checks that we're doing on the
values here. Uh the what I want you to
take away is that this is, you know,
we're doing a check here. If something
is wrong in our custom validator, we're
raising a value error with a message.
And if everything's fine then what we
return here. So this is doing validation
and normalization at the same time.
We're normalizing this uh by returning
all lowercase or it can be you know
whatever it is that you want to do. So
let me test this really quick. So I'll
add some uh uppercase letters here to my
username. So I'll say that the username
is Corey_chafer.
And now when I print this out, I no
longer just want to get that password
value. Uh let's print out the entire
user. So if I run this, then we can see
our username right here. So we can see
that our username gets validated and
also normalized to be all lowercase. Now
we could have added this extra alpha
numeric validation to our field section
but I just wanted to show some extra
validation here uh along with the
normalization as well. So let me scroll
back to our field validator. Now this is
an after validation which is the
default. That means that it runs after
paidantic has already done its type
checking and bas basic validation that
we've had before. But sometimes you want
to uh pre-process data before the main
validation happens. For that you use the
mode of before. Now let me show you an
example with the website field. Uh but
first let me show you how the current
website validation works. So, let me add
an invalid URL uh to my created user.
So, down here, um let's also add a URL
here. I'll say website is equal to uh
corems.com.
That might seem like it's valid, but if
I save this and run it, then you can see
that we get an error here. Now, the
reason that this is an invalid URL is
because it doesn't start with HTTP or
HTTPS.
Now, if I added that, then it would work
just fine. So, I'm going to add a custom
validator here so that if we pass in a
URL without an HTTP or HTTPS, then we'll
at least attempt to add that before the
URL is validated. So, let me grab this
from my snippets here. and then we will
explain what is going on. So here after
this username validator here, let me
make sure all my indents are right. Now
this is similar to the one that we had
before. We're saying that this is a
validator for our website field. We're
checking if there is a website value uh
since this field can also be none. uh
and if there is a value if it start
doesn't start with an HTTP or HTTPS
then we are just adding that in but
remember I said that by default these
custom validators are run after
paidantic has already done its type
checking and basic validation that we
have above. So, this isn't going to work
because it's going to do that HTTP URL
validation before uh this custom one is
even run. So, if I run this as it
currently is, you can see that we're
still getting the same error. So, what
we need to do here is we need to pass in
a mode of before. So here within our
field validator decorator, I'm going to
say mode is equal to and we'll set that
equal to before. That will tell pyantic
that we want to run this custom
validation and modification before it
does the HTTP URL validation uh that we
have above. It checks if the value
starts with HTTP or HTTPS. If not, it
adds that to beginning uh and then when
the HTTP URL validation runs, it'll get
that properly formatted URL. So if I run
this now, then we can see that this
works uh and that we have a proper URL
here. Now in addition to field
validators, we also have model
validators. These are useful when you
need to validate multiple fields
together or validate the complete model.
Uh, so a super common use case for this
is checking if a password and confirm
password field match. So again, let me
grab this as some code from my snippets
and then we will take a look at all of
this. So this is a good bit of code from
my snippets that I'm grabbing here. But
don't worry, I will uh explain all of
this as we go. So let me comment out the
user that we are creating here and let
me paste in this new model here. So let
me scroll up and I will explain this. So
what we've done here is we've created a
new model and it's just a simple user
registration model. So all this has is
an email that's set to an email string,
a password here which is a string and
then we also have a confirm password
field here as well. I just put these to
strings so that we can easily see this
example. Now we also have a validator
that checks if the password and confirm
password fields match. Now model
validators are a bit different here. Uh
instead of just receiving the value,
we're actually receiving self uh which
is the whole model instance and we can
access all the fields to do our
validation. So this runs after all the
fields have been validated individually.
That's why we're not using the uh class
method decorator anymore like we had
with the field validators. So down here
I'm creating a new user registration
here. And we can see that we are
creating an instance of a user
registration that does not have matching
passwords. So this has secret 123. This
is secret 456. So let me run this and we
can see that we get a validation error
here that says value error uh passwords
do not match. So using this model
validator we were able to uh check both
of those fields and compare those
together. Okay. So now let me talk about
some best practices for validators. So
first you should always return uh the
value even if you're not modifying it.
So the validator should either return
the value or raise an error. Uh second,
you'll usually want to raise a value
error for validation errors. Pyantic
will catch that and convert it to a
validation error automatically. You can
see that what we're catching down here
uh is a validation error, not a value
error. Uh but it's going to catch this
value error and turn that into a
validation error automatically for us.
And lastly, if you're going to raise an
error, uh don't mutate the value first.
So either return the modified value or
raise an error but not both. So that
covers some custom validators. So we've
seen field validators for individual
fields. Uh model validators for
validating the complete model and how to
access other fields during validation.
Uh these give you complete control over
your validation logic uh when the
built-in constraints that we saw before
aren't enough. Okay. So now let's talk
about computed fields. So sometimes you
want to have fields that are calculated
from other fields and you want them to
be included when you serialize your
model to a dictionary or JSON. So for
example, maybe you want a display name
that is generated from the first name
and last name or you want to calculate
if someone is an influencer based on
their follower count. Uh, by the way, I
hate the word influencer, but I just
wanted something quick and easy for this
tutorial. Uh I guess I could have called
it silver user or whatever. Uh but let's
ignore that. But anyways, Pyantic has
computed fields for exactly this kind of
purpose. So let me add computed field to
our imports here so that we can see what
this looks like. So from pyantic I'm
going to import computed
field. And now let's add some fields to
our user model that we can use for
computed values. And I'll add a first
name, last name, and a follower count.
Uh so let me grab these from my snippets
here. So first I just have these new
fields that I'm going to be adding in
here. So for the user, let me add these
here at the bottom so that we can see
them and indent them correctly. And let
me also remove full name here uh because
we are going to use a computed field to
compute a display name instead. And now
let me grab these computed fields from
our snippets and I'll add these to the
end of our model. So, let me grab these
and I will just add these to the end of
our model here. Make sure that we're
indented correctly. Okay, let's walk
through these. So, this is a property
decorator combined with a computed field
decorator. Now, I should have mentioned
this earlier, but the validator
decorators that we saw before and this
computed field decorator, these are
specific to paidantic. I mean, we
imported those. But the class method uh
decorator that we saw before and this
property decorator, these are related to
Python classes themselves. They're
outside the scope of this video. But if
you don't know what those decorators
mean or what they do, then I'd recommend
watching my playlist on Python classes
if you want to know uh exactly what
those do. And again, just like
everything else, I'll leave a link to
that playlist in the description section
below. Uh but for now, let's go over
these computed fields. So we can see
that our computed field here is display
name. And what we're doing in here is if
we have a first name and a last name, uh
then we just return those together uh
with a space in between. Uh if we don't
have the a first name and a last name,
then we just return the username. And
because we use this computed field, uh
this will be included when we serialize
our model. And I also included a more
simple one here just to really knock the
point home. This one doesn't even have
any conditionals or anything. It is uh
purely just a computed field. So we can
see that we have a computed field called
is influencer and all it does here is it
returns whether our follower count is
greater than 10,000. Okay. So let's test
these computed fields here. So uh let me
I'm going to uh comment out our user
registration here from before and let me
uncomment where we are creating a user
here. So right now if I run this as is
uh then you can see that we have a
display name here now and since we
didn't have a first name or a last name
that just returned our uh username and
that's how we had that set up. Um and we
can also see here that we have is
influencer equal to false because our
follower count by default is zero. uh
but let me add in a first name and last
name and we can see how that gets uh
computed differently if we have those
values. So I'll say first name is Corey.
We will set a last name equal to
Schaffefer. So now if I rerun this again
then we can see that we now have those
first name and last names in there and
we have the display name that is
basically that full name. So it's the
first name and last name with a space in
between. So these computed fields even
though we didn't specify these fields
explicitly um since they are computed
they get uh displayed whenever we
serialize this data. So that's nice to
have as an option. Um and these get
computed from other fields. So, also if
I wanted to update the follower count to
10,000 or so, then I could show you that
and get that coveted influencer status,
but I'm going to go ahead and skip past
that. Now, like I said before, uh these
computed fields will show up in our
dictionary and JSON dumps as well. Uh
so, let me dump this to JSON really
quick. So, I'll say uh model dump JSON.
And let's go ahead and set an indent of
two because I want to have this
formatted nicely here. So let's look at
this. And like I was saying before, uh
our uh computed fields of display name
and isfluencer um they get added to our
JSON output as well and also to the
dictionary output if I was to just do a
model dump. Okay. So another thing that
I wanted to talk about uh was about
using nested models. Now one of the more
powerful features of pyantic is that you
can use other pyantic models as field
types. So you can have models within
models and paidantic will automatically
validate everything recursively. Now
that might sound complicated but once
you see an example of this it's super
straightforward. So to demonstrate this,
let me add in a simple comment model
above our blog post model. So let me go
up to our blog post here. And again, I'm
going to grab this from my snippets
here. So I have a very simple comment
model here. So I will copy this and
paste this in here. So for a comment, we
just have some content, an author's
email, and the count uh a likes count.
So now let's update our blog post to use
nested models. Right now we have author
ID as just a string or an integer. We
can see that here. But what if we wanted
to store the full author information
instead of just this author ID? Well, we
can just use our user model as the type.
So, instead of having author ID here as
a string or an integer, I'm just going
to call this author and we will just
have this be a user type. So, now we'll
have this uh full user object nested in
our blog post. And let's say that a blog
post is also going to have a list of
comments. So to do this uh down here at
the bottom I will add another field here
called comments and let's have a list
and this is going to be a list of
comment which we created right here.
Okay. And I'm also going to have this be
a default factory of an empty list by
default. And if you think about it,
there's all kinds of different things
that we could add here. So, we could
even add a computed field like we saw
before that counts the number of
comments or something like that. Uh, but
I'm not going to worry about adding that
here. Uh, I'm just giving some ideas of
what you could easily add to this uh to
get some more useful functionality.
Okay. So, now we have a blog post that
contains a user as the author and a list
of comments. So now let's create a blog
post with all of this nested data. So
instead of creating this with keyword
arguments like we've been doing, let me
instead create this from a dictionary to
show what that might look like. Uh if
you receive this data from an API or a
form or something like that. So down
here I'm going to comment out uh what we
had before where we were creating this
user. And now let me go ahead and make
some space here. I'm also going to grab
this from my snippets as well. This is a
little bit longer here since we are
creating a big dictionary of data here.
Let me paste this in. Okay, so let me go
over what this post data dictionary
looks like. So we can see that we have
uh we are creating a blog post here. So
we have a title, content, a slug. All of
that is perfectly fine and normal. Once
we get to the author, we have a nested
dictionary here. And this is going to be
a user. So now we have a user here and
it has to pass all the user validation
checks. So we have a username of corems,
email, age, password, things like that.
For comments, we now have a list here.
And this is a list of comments. And each
one of these comments has to pass the
comment validation. So for the comment
validation, we have content, author,
email, likes, uh, and I think that's all
we had for comments. So now we're taking
that post data dictionary and unpacking
that to create a blog post. And
Pideantic will automatically validate
everything recursively. So, not only
does it validate the blog post fields,
but it also makes sure that our user and
comment fields are good, too. Now, this
is one way that you'll see it done, but
if you're unfamiliar with unpacking like
this with the asterisk uh or lists in
Python, then another way that you can do
this is by saying uh blog post.od
model
validate and then just pass in that
directly without unpacking that. And
those are essentially doing the exact
same thing. They're just creating a blog
post from our post data. So after we're
creating that blog post, I am uh
printing this out in a JSON format. So
let's run this and see what this looks
like. So we can see that that works. Not
only did it create our blog post, uh,
but we also have all of this user
information as well, uh, so we have the
autogenerated,
uh, unique ID for the author. Uh,
there's the autogenerated uh, timestamp
for our blog post that we have here. Uh,
and all of the optional fields for all
of these models. So nested models gives
you a really powerful way to structure
complex data with automatic validation
all the way down. Okay, so we're just
about finished up here. Uh but let's
talk about some more advanced serial
serialization and working with JSON. So
so far we've been using model dump and
model dump JSON, but there are a lot
more options for controlling how your
data is serialized. One common use case
is working with APIs where the external
representation uh representation uses
different field names than your internal
Python names. So for example, maybe
you're working with a JavaScript
frontend that uses camel case, but you
want snake case in Python. Or maybe you
have internal fields that you don't want
to expose when serialized. So to show
you some of this, we're going to need to
import config dict from paidantic. So
let me go up here and do that. So from
paidantic, we are uh importing config
dict. Okay. And this is going to allow
us to configure our models further. Uh
let me actually copy that there. So for
example, let's configure our user model.
So to do this, we'll add an attribute to
the model called model
config. And we'll set this equal to that
config dictionary there. And now we can
configure our model by passing in
arguments to this config dictionary
class. Uh the first one that I'll set is
populate by name. So that is populate by
name. I will set that equal to true. And
this tells Pantic to accept both the
field name and an alias when loading
data. So for example uh for an ID we are
using UID for the field name which is
usually what you want to do in Python
because like I said before ID is a
built-in keyword that we don't want to
overwrite. But some of the data that we
load in might actually use uh ID as the
field name. So let's add that as an
alias. So here at the beginning I'm
going to say alias is equal to ID. So
again quick recap there. So first we
have this model config uh and we have
this populate by name set to true. This
tells paid to accept both the field name
and the alias when loading data. So that
allows us to be a little more flexible
when loading or exporting data with
different naming conventions. So, let me
grab some more data to load in here. And
all of our commented out code down here
is getting a little messy. So, let me go
ahead and just overwrite all of this
right now. And then I will paste in some
new stuff from our snippets here. Okay.
So, from our snippets here, let me grab
a little bit more here. Like I said,
we're getting close to the end, so there
are no more snippets after this. Okay.
Okay, so let me go over what we pasted
in here. So you can see that the user
data that we're loading in uh uses this
ID field instead of UID like we have
specified in our Python model. Uh but
since we have model config with populate
by name set to true and told that field
uh the UID field to use ID as an alias,
then I should be able to run this just
fine. So if I save this and run it, then
we can see that that works and that we
get that uh unique ID that we passed in
there. Those match up. But you can also
see that when it outputs the user, it's
still using our field names. So it's
still using uh UID here. So even though
it was able to load in that data just
fine, it's still outputting our own
field names. And that might be what we
want. uh but if you want it to use the
aliases on output also then we can just
specify that uh whenever we dump that
out. So whenever I'm dumping this out
here I'm already passing in an indent of
two but I can also uh pass in a by alias
equal to true. And now if I run this you
can see that instead of UID we are
getting ID. So when we're sending data
to a front-end or external API, uh we
can use the aliases to match their
naming conventions. So now let's look at
excluding and including specific fields.
So sometimes you don't want to serialize
everything. Uh maybe you have sensitive
data or internal fields that you want to
exclude. So for example, even though
it's blurring out our password already,
uh let's say that we don't want to
include it in our output at all. Let's
just exclude the password from
serialization. So to do that, we can
just say here with our when we are
dumping this out, I will exclude that
password and this is going to be in
brackets here. So I will exclude that
password. If I save that and run it,
then you can see that we no longer have
that password. It was excluded. Now, the
inverse of that, if we wanted our output
to be limited to only a few fields and
we don't want to go through and exclude,
you know, a whole bunch of fields and we
just want to include some uh then what
we can do is we can just be explicit
about what we want to include. So let's
say that I just wanted to include the
username and email in our output here.
What I could do is I could just say
include and let's say that we just want
the username and also the email. So if I
save that and run it, then you can see
that now all we have are those two
included fields and everything else is
excluded. Now, another thing that you
might end up doing a lot is loading in
data from JSON directly. So far, we've
been creating users directly or loading
them in from Python dictionaries. But
let's create a user directly from a JSON
string. So, to do this, I'm going to
import the built-in uh JSON library here
or the JSON module. So, I'll import
that. Scroll back down here to the
bottom. And now if our data was a JSON
string instead of a dictionary, we could
load that in with model validate uh JSON
instead of just model validate. So model
validate_json.
And now this is going to be expecting a
JSON string. Uh what I'll do here is I
will just use that JSON module to dump
our current dictionary to a JSON string.
Now in a real world example, that JSON
string would be coming from a different
source because if you already had the
Python dictionary, then you'd just load
that in directly like we did before. Um
so you wouldn't need to do this. Uh but
if I run this just to show you that that
does work, uh we can see that that
validates just fine. So this model
validate JSON method is really useful
when uh you're receiving JSON data from
an API or reading from a file. So these
serialization features give you a lot of
flexibility when working with external
systems. Uh you can use aliases for
different naming conventions. Uh control
which fields get serialized with include
and exclude and easily convert between
your models and JSON. Now let's talk a
little bit more about model
configuration. So Pyantic gives you a
lot of control over uh how your models
behave through the model config
attribute uh that we saw up here. Where
was that? It was in the user module.
Okay, right here. So we've seen this a
bit with the populate by name uh that we
just now used. But there are a lot more
options. And I should mention uh that in
pyantic version one uh you would use the
config class for this. But in version
two uh we use this model config with
config dict like we see here. But if you
see in older code someone using the
config class uh just know that that's
the old way. Now one of the most common
configuration options is controlling
type coercion. Remember how pyantic
normally coerces types uh like
converting the string uh 39 to the
integer of 39? Well, sometimes you want
stricter validation where no type
coercion happens at all. Uh to do this,
it's just as easy as adding uh strict is
equal to true to our config dict here.
So, let me add that really quick. I'll
say strict is equal to true. And let's
put this on some new lines here so that
we can see uh all of these
configurations. So now the data that
I've been using to create this user um
has an age of 39, but it's coming in as
a string. So pyantic has been converting
this to an integer. But now with strict
equal to true, it should reject this and
tell us that an integer is required. So
if I save this and run it, you can see
that this string of 39 was working uh
before. But now it's saying that our
input should be an integer. So now uh we
are in that strict mode. Now if I was to
change this to be an integer of 39
instead. Save that and run it. Then you
can see that that works just fine. And
also we're just printing out that
username and email uh just so I can uh
show all this. Let me go ahead and print
out that entire user again. Okay, there
we go. So that strict mode is useful
when you want to be very explicit about
types and catch cases where you're
receiving the wrong type of data. Uh you
can also use uh strict types for
individual fields like strict string or
strict integer if you only want strict
validation on certain fields rather than
on the whole model. Now another useful
configuration is the extra option which
uh controls what happens when you pass
fields that aren't defined in your
model. Uh by default paidantic just
ignores extra fields but we can change
that. So if I wanted to be able to add
extra fields, then I could uh come up
here to our user model here and where we
have our configurations, I can say extra
equals allow. And now with the data I
was loading in, if I add extra fields
that aren't explicitly in our model,
then it should accept them. Uh so let's
add a note to our user here. So I'll
just add in another field called notes
and let's just set this equal to um I
don't know kind of a Karen. So with
extra equals allow that we have in our
configuration any extra fields that we
pass in uh should be preserved. So this
can be useful for things like plug-in
systems or when you want future
compatibility where new fields might be
added. Um, so if I run what we have here
now, then we can see right here we have
our extra field uh that was included
even though that field's not explicitly
uh in our user model. Now you could also
set extra to forbid if you want to raise
an error when extra fields are passed uh
which is useful when you want to catch
typos or ensure that you're only getting
expected fields. Or you could use ignore
which is the default behavior uh of
paidantic where the extra fields are
accepted but just silently ignored. And
now another one that we can change here
is validate assignment. So remember
earlier when I mentioned that by default
paidantic doesn't revalidate when you
change a field after it's uh created.
Well, you can change that within the
configuration. So if I go up to the
configuration here, if I add in uh
validate assignment here and set that
equal to uh true, then with this
configuration set, it'll revalidate any
time that we make changes uh which it
didn't before. So if I go down here to
the bottom after our user is created, if
I change this user.e
and change this to something that isn't
an email, so something like Corey M.
Schaefer, if I run this now, then you
can see that we are getting a validation
error here uh because it is revalidating
when uh on assignment whenever we change
values. So that is not the default
behavior of paidantic. You actually have
to uh set that to have it revalidate. So
that is super useful when you have
models that uh get modified after
creation and you want to ensure that
they stay valid. And one more
configuration that I want to show you
here is frozen models. So sometimes you
want to create immutable models where
the data can't be changed at all after
creation. So this can be useful for
things like configuration objects or
just when you want to ensure that data
integrity and that nothing changes. So
up here let me change this validate
assignment setting and instead of
validate assignment I'll set this as
frozen equals true. So now if I rerun
this I'm still going to get an error
here. Uh but the error is going to be
because the instance is frozen, not
because the email doesn't validate. Even
if the email validated. So let me do
cordiumshaper 2@gmail.com.
So that is a valid email. Even if the
email is valid, if I run this, we're
still going to get uh a validation error
here because our instance is frozen and
we can't make changes to it after it's
been created. So that can be super
useful if that's what you want and we uh
and you don't want to make any changes
once that's been created. And frozen
models can also have uh a small
performance benefit since Pyantic knows
that the data isn't going to change. So
these configuration options that we've
seen, they give you a lot of control
over how your models behave. So you can
make validation stricter to prevent
surprises. uh validate on assignment to
keep uh data valid after it's created.
Um control how extra fields are handled
and make models immutable uh when you
need that guarantee. Okay, so that
covers everything that I wanted to look
at for model configurations. So I know
that we've covered a lot in this
tutorial, but I hope that you see uh why
pyantic is so useful for data validation
and the flexibility that we have here.
Uh, and the best part is that Pyantic
does all of this with pretty minimal
boilerplate. So you just add type hints
to your models and Pantic handles the
rest. And those type hints aren't just
for validation. They also give you great
IDE support uh with autocomplete and
inline documentation. So your code
almost becomes self-documenting. Now
Pyantic also has a really strong
ecosystem. So if you're interested in
building web APIs, fast API uses
paidantic under the hood for request and
response validation. Uh SQL model which
is a library for working with SQL
databases. It also uses pyantic models.
Uh so once you learn pyantic then you're
you're going to see it pop up everywhere
in the Python ecosystem. Uh, in the next
videos I'm going to plan on covering
fast API which will show you how Pantic
is used in a realworld web framework. So
be sure to subscribe so that you don't
miss that. But I think that is going to
do it for this video. Hopefully now you
have a good idea of how to use Pyantic
for its powerful data validation in
Python. But if anyone has any questions
about what we covered in this video,
then feel free to ask in the comment
section below and I'll do my best to
answer those. And if you enjoy these
tutorials and would like to support
them, then there are several ways you
can do that. The easiest way is to
simply like the video and give it a
thumbs up. Also, it's a huge help to
share these videos with anyone who you
think would find them useful. And if you
have the means, you can contribute
through Patreon or YouTube, and there
are links to those pages in the
description section below. Be sure to
subscribe for future videos, and thank
you all for watching.
In this video, we'll be learning how to use Pydantic, Python's most popular data validation library. Pydantic uses type hints to validate data at runtime, ensuring that the data coming into your application meets your expectations. We'll cover everything from basic model creation and field validation to custom validators, type coercion, nested models, and model configurations. We'll also see why Pydantic is so widely used in libraries like FastAPI, data processing pipelines, and AI tools. If you've ever struggled with messy manual validation code or data that isn't the right type or format, Pydantic will make your life much easier. Let's get started... The code from this video can be found here: https://gist.github.com/26fbfae9fb2ad293cc431530e8932855 Type Hinting tutorial - https://youtu.be/RwH2UzC2rIo UV tutorial - https://youtu.be/AMdG7IjgSPM Mutable Default Arguments video: https://youtu.be/_JGmemuINww Regular Expressions tutorial: https://youtu.be/K8L6KVGG-7o Python Object-Oriented playlist: https://www.youtube.com/playlist?list=PL-osiE80TeTsqhIuOqKhwlXsIBIdSeYtc Pydantic documentation: https://docs.pydantic.dev/ Pydantic types reference: https://docs.pydantic.dev/latest/api/types/ More Pydantic types: https://docs.pydantic.dev/latest/api/networks/ ✅ Support My Channel Through Patreon: https://www.patreon.com/coreyms ✅ Become a Channel Member: https://www.youtube.com/channel/UCCezIgC97PvUuR4_gbFUs5g/join ✅ One-Time Contribution Through PayPal: https://goo.gl/649HFY ✅ Cryptocurrency Donations: Bitcoin Wallet - 3MPH8oY2EAgbLVy7RBMinwcBntggi7qeG3 Ethereum Wallet - 0x151649418616068fB46C3598083817101d3bCD33 Litecoin Wallet - MPvEBY5fxGkmPQgocfJbxP6EmTo5UUXMot ✅ Corey's Public Amazon Wishlist http://a.co/inIyro1 ✅ Equipment I Use and Books I Recommend: https://www.amazon.com/shop/coreyschafer ▶️ You Can Find Me On: My Website - http://coreyms.com/ My Second Channel - https://www.youtube.com/c/coreymschafer Facebook - https://www.facebook.com/CoreyMSchafer Twitter - https://twitter.com/CoreyMSchafer Instagram - https://www.instagram.com/coreymschafer/ #Python #Pydantic