-: Hi guys and welcome back.
In this video, we're going to start learning
about decorators.
Decorators are great in Python
because they allow us to very easily modify functions.
So let's learn more about them.
Here we've got a user dictionary that has a username and
an access level of guest.
Let's say that guests can't access this function here
get_admin_password
which returns the password to your admin panel.
But as you can see,
if we print out get_admin_password
at the moment, even though we're a guest
we're gonna get out 1234.
There's nothing in this function
to prevent us from doing that.
So, the output of this video should be
to get this function secured so that
people that are guests cannot get the value out.
Now of course, the clear thing that you can do
is to put an if statement in here.
If the user's access level is == to admin,
then you can print out this function.
Otherwise, you can't.
So this will work
if you run that, you don't get anything,
but of course as you know,
get_admin_password is still un-secure
so you can still print that out
and it'll still work.
Because what we've done is
we have protected this specific call to get_admin_password,
but we haven't protected this one.
So, this is probably not exactly what we want to do.
The next thing we can do
is we can define a secure function.
For example, secure_get_admin.
And in this one, if the user's access level is admin
then we can return the password 1234.
Now, a couple of things.
Clearly get_admin_password is still defined,
so that is not going to work,
but secure_get_admin does protect the password.
Running that gives you None, first of all
when you run secure_get_admin
because we didn't return 1234.
Instead, we returned None which is a default.
Then you get 1234 the next time around.
So of course, what you may want to do
is delete this one entirely.
But, that poses a small problem.
Which is that all the functions
where you want it to be secure,
you're going to have to add this if statement.
And up 'til now, that is what you would have done
if you didn't know about decorators.
But, a decorator will allow us to modify this function
to secure it without having to replace
all of our functions by its secure counterparts.
So, we don't want to do that either.
Instead what we could do
is define another function, secure_function
that takes in a function.
We've learned about first class functions,
so we can take it in as a function there,
and we'll say if the user's access level is == to admin
then we will return func.
Otherwise we'll return none.
And now what you may be tempted to do
is to do something like
get_admin_password = secure function(get_admin_password).
So, will this work?
Well, let's run through the code.
We've got secure_function being called
which takes in get_admin_password,
and when this line runs,
we're going to check the user's access level
and see if it's admin.
And if it is, we're going to return func.
So, let's see what happens.
Now you get an error.
NoneType object is not callable.
Because of course, when we ran this code
i.e., this line here,
the user's access level was guest not admin.
So we didn't return func,
we returned none which is the default.
So get_admin_password = none.
Then we're trying to run none as if it were a function,
but you can't do that so you get an error.
What you would have to do is
make sure that your user is an admin
before you run any of your code.
And then it will work and you will get 1234.
So this is a step closer to what we want,
but alas it requires that our user be an admin
before we secure our functions.
Ideally we would like to do something that checks
the user's access level when you call the function,
not when you define it.
So, here's what we're gonna do.
We're still going to take in a function
but now we're going to define another function,
which I'm going to call secure_function.
This one I'm gonna call make_secure.
This one doesn't take any parameters,
and it is the one that checks.
And, it returns calling the original function.
Then, here we return secure_function.
So this is a decorator.
What happens now, and by the way
this should be make_secure.
What happens now is that get_admin_password,
this function that we want to secure,
is passed to the make_secure function.
This one defines another function,
and by the way, in Python you can define functions
inside a function, so that's totally fine.
And this function here, when called,
will check the user's access level
and return calling the original function
which is get_admin_password.
So it will return 1234 if the user's access level is admin.
So that's this function here,
and then we return the function itself.
Not the function call, but the function itself.
get_admin_password will be equal to this function.
Which calls seemingly itself, but this is okay.
It calls get_admin_password from up here.
So when you call get_admin_password what you're gonna do
is you're going to check the user's access level,
and then you're going to return the result
of calling the function originally,
which will give you 1234.
At the moment you get none, but of course if you
set the user's access level to admin down here
and you run it again,
then you get 1234.
Which is exactly what we wanted.
So this is a simple decorator.
This simple decorator will create a function
and replace the original function with this secure one.
So that you can no longer call get_admin_password
without having the admin access level.
Now if you wanted to add a little bit of error handling
you can do an else here
and you can return something like
"No admin permissions for {user['username']}."
and that'll give you a nicer idea of what's going on
if you don't have admin permissions.
That's everything for this video.
This is a simple decorator,
and now in the next one we're going to learn more
about using the @ syntax for decorators
that makes this much simpler, and much easier
to re-use and secure multiple functions
if that's what you want to do.
Thanks for joining me,
and I'll see you in the next video.