tutorial on removing redundant code in the Django framework using the Python adorner

Source: Internet
Author: User
Tags repetition
The Python adorner is a powerful tool for eliminating redundancy. With the ability to block functionality into the right size, even the most complex workflow, adorners can make it a neat feature.

For example, let's look at the Django Web framework, which processes the requested method to receive a method object and returns a response object:

def handle_request (Request):  return HttpResponse ("Hello, World")

I recently encountered a case where I need to write several API methods that meet the following criteria:

    • Return JSON response
    • If it is a GET request, then the error code is returned

As an example of registering API endpoints, I will write like this:

def register (Request):  result = None  # Check for post only  if request.method! = ' Post ':    result = {"Error": "This method is only accepts posts!"}  else:    try:      user = User.objects.create_user (request. post[' username '),                      request. post[' email ',                      request. post[' password ')      # Optional Fields      for field in [' First_Name ', ' last_name ']:        if field in request. POST:          setattr (User, field, request.) Post[field])      user.save ()      result = {"Success": True}    except Keyerror as e:      result = {"error": Str (e) }  response = HttpResponse (Json.dumps (Result))  if "error" in result:    Response.status_code =  return response

However, I will write the JSON response and the code returned by the error in each API method. This will result in a lot of logical repetition. So let's try to implement the dry principle with adorners.

Introduction to Adorners

If you are unfamiliar with adorners, I can simply explain that the adorner is actually a valid function wrapper, the Python interpreter loads the function and executes the wrapper, and the wrapper can modify the function's receive parameters and return values. For example, if I want to always return an integer result that is older than the actual return value, I can write the adorner like this:

# A decorator receives the method it ' s wrapping as a variable ' F ' def increment (f):  # We use arbitrary args and keyword s to  # Ensure we grab all the input arguments.  def wrapped_f (*args, **kw):    # Note We call F against the variables passed into the wrapper,    # and cast the result to an int and increment.    return int (f (*args, **kw)) + 1  return Wrapped_f # The wrapped function gets returned.

Now we can use the @ symbol and this decorator to decorate another function:

@incrementdef Plus (A, b):  return a + b result = Plus (4, 6) assert (result = = one, "We wrote our decorator wrong!")

The adorner modifies the existing function to assign the result of the adorner's return to the variable. In this example, the result of ' plus ' actually points to the result of increment (plus).

Error returned for non-POST request

Now let's apply adorners in some of the more useful scenarios. If it is not the POST request that is received in Django, we return an error response with the adorner.

Def post_only (f): "" "  ensures a method is post only" ""  def Wrapped_f (request):    if Request.method! = "POST": 
  response = HttpResponse (Json.dumps (        {"Error": "This method is only accepts posts!"})      Response.status_code = return      response    return F (Request)  return Wrapped_f

Now we can apply this adorner in the registration API above:

@post_onlydef Register (Request):  result = None  try:    user = User.objects.create_user (request. post[' username '),                    request. post[' email ',                    request. post[' password ')    # Optional Fields    for field in [' First_Name ', ' last_name ']:      if field in request. POST:        setattr (User, field, request.) Post[field])    user.save ()    result = {"Success": True}  except Keyerror as e:    result = {"error": Str (E)}  response = HttpResponse (Json.dumps (Result))  if "error" in result:    Response.status_code =  return response

Now we have an adorner that can be reused in each API method.

Send JSON response

In order to send a JSON response (while processing a 500 status code), we can create another decorator:

Def json_response (f): "" "  return the response as JSON, and return a of error code if an error exists" ""  def Wrap PED (*args, **kwargs):    result = f (*args, **kwargs)    response = HttpResponse (Json.dumps (Result))    if Type ( result) = = Dict and ' error ' in result:      Response.status_code = 500return response

Now we can remove the JSON-related code from the original method and add an adorner instead:

Post_only@json_responsedef Register (Request):  try:    user = User.objects.create_user (request. post[' username '),                    request. post[' email ',                    request. post[' password ')    # Optional Fields    for field in [' First_Name ', ' last_name ']:      if field in request. POST:        setattr (User, field, request.) Post[field])    user.save ()    return {"Success": True}  except Keyerror as E:    return {"Error": Str (E)}

Now, if I need to write a new method, I can do redundant work with adorners. If I want to write a login method, I just need to have a positive correlation code:

@post_only @json_responsedef Login (Request):  If Request.user is not None:    return {"Error": "User is already authenticated! "}  user = Auth.authenticate (request. post[' username '), request. post[' password ')  if user is not None:    if not user.is_active:      return {"Error": "User is inactive"}    Auth.login (Request, user)    return {"Success": True, "id": user.pk}  Else:    return {"Error": "User does not exist with those credentials "}

BONUS: Parameterize Your request method

I have used the Tubogears framework, where request parameters are interpreted directly to convey the method which I like. So how do you imitate this feature in Django? Well, adorners are a solution!

For example:

def parameterize_request (types= ("POST",)): "" "  parameterize the request instead of parsing the request directly .  Only the types specified is added to the query parameters.   e.g. convert A=TEST&B=CV in Request. POST  to F (a=test, B=CV)  "" "  def wrapper (f):    def wrapped (request):      kw = {}      if ' GET ' in types:< C10/>for K, v in Request. Get.items ():          kw[k] = v      if "POST" in types:        for K, V in Request. Post.items ():          kw[k] = v      return f (Request, **kw)    return wrapped  return wrapper

Note that this is an example of a parameterized adorner. In this example, the result of the function is the actual adorner.

Now I can write the method with parametric adorner! I can even choose whether to allow get and post, or just a request parameter type.

@post_only @json_response@parameterize_request (["post]") def register (request, username, email, password,       first_ Name=none, Last_name=none):  user = User.objects.create_user (username, email, password)  user.first_name= First_Name  user.last_name=last_name  user.save ()  return {"Success": True}

Now we have a simple, easy-to-understand API.

BONUS #2: Save docstrings and function names using Functools.wraps

Unfortunately, one side effect of using adorners is that the method name (__name__) and docstring (__doc__) values are not saved:

def increment (f): "" "  increment a function result" ""  Wrapped_f (A, B):    return F (A, B) + 1  return wrapped_ F @incrementdef Plus (A, b)  "" "Add Things Together" ""  return a + B plus.__name__ # This was now ' Wrapped_f ' Instead of ' Plus ' plus.__doc__ # this-now  returns ' Increment a function result ' instead of ' Add-a-things '

This will cause problems with applications that use reflection, such as Sphinx, an application that automatically generates documents.

To solve this problem, we can use the ' wraps ' adorner to append the name and docstring:

From Functools import wraps def increment (f): "" "  increment a function result" ""  @wraps (f)  Wrapped_f (A, B): C3/>return F (A, B) + 1  return wrapped_f @incrementdef Plus (A, b)  "" "Add, Things Together" "" "  return a + b PLUS.__NAME__ # This returns ' Plus ' plus.__doc__  # This returns ' Add both things together '

BONUS #3: Using ' decorator ' adorner

If you take a closer look at the way the adorner is used, there is a lot of repetition in the wrapper declaration and return place.

You can install the Python egg ' decorator ', which contains a ' decorator ' adorner that provides an adorner template!

Using Easy_install:

$ sudo easy_install decorator

or PIP:

$ pip Install decorator

Then you can simply write:


From decorator import decorator @decoratordef post_only (f, request): "" "  ensures a method was post only" "  if requ Est.method! = "POST":    response = HttpResponse (Json.dumps (      {"Error": "This method is only accepts posts!"}    ) Response.status_code =    return response  return F (Request)

The more awesome thing about this decorator is that it preserves the return value of __name__ and __doc__, which encapsulates the functionality of Functools.wraps!

  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.