Python decorator implements several types of validation function practice examples

  • 2020-06-01 10:15:42
  • OfStack

A new requirement has recently emerged to add several resource permissions to the system. Minimize code changes and program complexity. So still use adornment implement to compare science

We used some readymade decorator modules for login verification. Then copy and write some permission decorators for the user administration section.

Like the following


def permission_required(permission):
  def decorator(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
      if not current_user.can(permission):
        abort(403)
      return f(*args, **kwargs)
    return decorated_function
  return decorator

def admin_required(f):
  return permission_required(Permission.SMY)(f)

It's easy to understand when calling permissions. Just copy admin_required. Then each page entry is written with syntax sugar like this: @admin_required

So the access rights of the page are done. But resource permissions are different from page permissions. The permission mentioned above is written in the static content of model.py.

From the encapsulation point of view, at least it is not obvious where the user query method is exposed (at the novice level). It's easy to see that if seems to be using the built-in method for the current_user variable

But current_user is actually the content of a 3rd party package, the same package introduced by the login module, which is a complete set of code to record token information. Too much detail. So let's write it from here, go die

Because even though I know I'm actually calling.can (permission) is a class method defined in the model class. But it is not clear which part of current_user is taking.

So let it go. Comb the content of the decorator from the beginning.

First of all, a simple decorative writing method is very easy to understand. For example, the original function was written like this:


def page():
  if user == 'admin':
    form = Form()
    
    if request.method=='POST':
      
      db.session.add(form)
      db.session.commit()
      flash("success")
      return 0

This is of course a random function (obviously with a lot of problems), just to express a process. The first if judgment is executed when this function is first invoked via routing. This judgment is what we want to verify

Once the validation is done, the user can access the page, the content will be rendered, and the interaction will be allowed...

The decorator, then, is to extract the function of this if. So the original function would look something like this:


@admin_check
def page():
    form = Form()

    if request.method=='POST':

      db.session.add(form)
      db.session.commit()
      flash("success")
      return 0

In terms of this function alone, it doesn't make any sense to write it this way, as if it took a few more lines of code to solve the problem in the first place. Let's expand the full code in this form to take a look at 1:


def admincheck(func):
  if user=='admin':
    return func
  
def page():
    form = Form()

    if request.method=='POST':

      db.session.add(form)
      db.session.commit()
      flash("success")
      return 0

page = admincheck(page())

The decorator above just wrote page=admincheck in @ mode.

But this only solves the most basic validation problem. That is, relatively independent entry validation. This validation has not yet taken the arguments passed by the program to the page() function. In other words, this validation doesn't look very useful, right

But here's the mechanism. The next step is to examine how to validate the request data that is routed to you and then proceed.


def admincheck(func):
  def inner(arg):
    if user == 'admin':
      if arg == 'false':
        abort(403)
    return func(arg)
  return inner

Similarly, for multiple parameters, just rewrite def inner(arg) to def inner(arg1,arg2)

When n is a parameter, def inner(*args,**kwargs) is written as def (*args,**kwargs). *args is a tuple, that is, ('user',1); **kwargs is the dictionary, i.e. {'user':1}

By writing both parameters at the same time, you can basically handle all the parameter types that are passed in.

Of course. In addition, there are more complex decorative writing. However, it can handle passed arguments without affecting the normal execution of the decorated function. Basically implemented the previous function.

So let's go back to our example. The outermost layer USES def permission_required(permission): for obvious reuse purposes.


def admin_required(f):
  return permission_required(Permission.SMY)(f)

The (permission) parameter above obviously corresponds to the (Permission.SMY) parameter in permission_required(Permission.SMY). The parameter parameter is passed into the body of the method

This is also why it is important to nest a layer of functions outside the decorator decorator(f) -- reuse

So it's pretty clear what this is


def permission_required(permission):

# This is done through formal parameters 1 Decorator class. The implementation of this function can be called for any specific decorator with minimal modification (passing parameters) 
  def decorator(f):
  # This is where the decorator starts 1 step 
    @wraps(f)
    # This decorator is actually meant to keep the original properties of the function unchanged. By primitive, I mean __name__  This attribute 
    def decorated_function(*args, **kwargs):
    # This decorator method inherits the formal parameters of the original function. So you're essentially adding this function to the beginning of the function 
      if not current_user.can(permission):
      # This place is obvious. current_user Fetch (server side) from memory, and then permission It will be verified according to our actual needs permission The transformation from formal parameter to argument is carried out 
        abort(403)
        # Obvious exception handling, of course, 403 is 1 A rough approach. The rougher way, I'll do it redirect(url_for(logout))...
      return f(*args, **kwargs)
      # Close the call and pass the argument to the function (here f() Is the original function (more specific permission verification decorator), just f It's just an ugly parameter.) 
    return decorated_function
  return decorator

That's pretty much it. If anyone would like to add, please feel free to leave a comment.


Related articles: