tutorial on removing redundant code from the Django frame using the Python adorner _python

Source: Internet
Author: User
Tags auth repetition wrapper

The Python adorner is a powerful tool for eliminating redundancy. As the functionality is modular to fit, even the most complex workflows, adorners can make it a neat feature.

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

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

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

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

As an example of a registered API endpoint, 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 error return code in each API method. This can lead to a lot of logical repetition. So let's try to use the adorner to achieve the dry principle.

Introduction to Adorners

If you are not familiar with the adorner, I can briefly explain that, in fact, the adorner is a valid function wrapper, the Python interpreter loaded the function will execute the wrapper, the wrapper can modify the function of the receive parameters and return values. For example, if I want to always return an integer result that is greater 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 Ke Ywords to
  # Ensure we grab all the input arguments.
  def wrapped_f (*args, **kw):
    # We call F against the variables passed to the wrapper, # and cast the result
    to a int and increment.
    return int (f (*args, **kw)) + 1 return
  Wrapped_f # The wrapped function gets returned.

Now we can decorate another function with the @ symbol and the adorner:

@increment
def plus (A, b): return
  A + b result
 
= Plus (4, 6)
assert (Result = = one, "We wrote our Decorat or wrong! ")

The adorner modifies the existing function and assigns the result returned by the adorner to the variable. In this example, the result of ' plus ' actually points to the result of increment (plus).

Return error for non POST request

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

Def post_only (f): "" "
  ensures a method are 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 above registration API:

@post_only
def 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 adorner:

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

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

Post_only
@json_response
def 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 the redundancy work with the adorner. If I'm going to write the login method, I just need to have the code in the photo positive:

@post_only
@json_response
def login (Request):
  If Request.user isn't 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: Parameterization of Your request method

I have used the Tubogears framework, where the request parameters are interpreted directly to the forwarding method which I like very much. So how do you imitate this feature in Django? Well, the decorator is 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:< C11/>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 the parameterized 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 there are no saved method names (__name__) and docstring (__doc__) values:

def increment (f): "" "
  increment a function result" "
  Wrapped_f (A, B): Return
    F (A, B) + 1 return
  Wrapp Ed_f
 
@increment
def plus (A, b)
  "" "Add Two things Together" "" Return
  A + b
 
plus.__name__ # is now ' wrapped_f ' instead of ' plus '
plus.__doc__  # This now returns ' Increment a function result ' instead of ' a DD Two things together '

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 attach the name and docstring:

From Functools import wraps
 
def increment (f):
  "" "Increment a function result" "
  @wraps (f)
  Wrapped_f (A , b): Return
    F (A, B) + 1 return
  wrapped_f
 
@increment
def plus (A, b)
  "" "Add Two things Together" "" 
   return A + b
 
plus.__name__ # This returns ' Plus '
plus.__doc__  # This returns ' Add two things '

BONUS #3: Using the ' 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 the return place.

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

Use Easy_install:

$ sudo easy_install decorator

or PIP:

$ pip Install decorator

Then you can simply write:


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

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

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.