Updated "Introduction to Python Web Application Homebrew for Slow 3rd Year Web Engineers"

Updated the book

Chapter "Create Request / Response / View class to improve visibility" The has been updated.

If you want to read more, please "like" or "follow me" in Book ;-)


The following is an excerpt of the contents of the book.


Refactor

There are now three endpoints that generate dynamic responses, and workerthread.py is now close to 200 lines.

Even at this point, I'm doing a lot of different things with one file, so even 200 lines has become a messy module with very poor visibility.

What's more, as you evolve this web application, you will have more and more endpoints. It is obvious that maintenance will not be possible if you add it to workerthread.py each time. It can be said that it has become necessary to improve the visibility of workerthread.py by separating responsibilities and dividing files.

In other words, it's about time ** the season for refactoring has arrived **.

In this chapter, we will cut out "processing that dynamically generates a response body for each endpoint" to an external module.

STEP1: Simply cut out as a function

First, let's simply cut out the HTML generation process for each endpoint into another module.

The name of the module to cut out is views. This is because it is a module whose responsibility is only to generate the view part (= request body), regardless of the HTTP situation such as connection or header parsing.

Source code

study/workerthread.py https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter16/workerthread.py#L50-L59

study/views.py https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter16/views.py

Commentary

study/workerthread.py

Lines 57-66

            if path == "/now":
                response_body, content_type, response_line = views.now()

            elif path == "/show_request":
                response_body, content_type, response_line = views.show_request(
                    method, path, http_version, request_header, request_body
                )

            elif path == "/parameters":
                response_body, content_type, response_line = views.parameters(method, request_body)

Until the last time, I wrote that the process of generating HTML was sticky for each path, but first I decided to cut out that part to the function of the views module.

by this,

--workerthread.py receives the HTTP request, parses it, gets the response contents from the function of the views module according to the path, constructs the HTTP response, and returns it to the client. --views.py has a function according to each path, receives the contents of the request and returns the contents of the dynamically generated response.

The task of "dynamically generating the content of the response according to the path" was cut out in views.

study/views.py

import textwrap
import urllib.parse
from datetime import datetime
from pprint import pformat
from typing import Tuple, Optional


def now() -> Tuple[bytes, Optional[str], str]:
    """
Generate HTML to display the current time
    """
    html = f"""\
        <html>
        <body>
            <h1>Now: {datetime.now()}</h1>
        </body>
        </html>
    """
    response_body = textwrap.dedent(html).encode()

    # Content-Specify Type
    content_type = "text/html; charset=UTF-8"

    #Generate response line
    response_line = "HTTP/1.1 200 OK\r\n"

    return response_body, content_type, response_line


def show_request(
    method: str,
    path: str,
    http_version: str,
    request_header: dict,
    request_body: bytes,
) -> Tuple[bytes, Optional[str], str]:
    """
Generate HTML to display the contents of the HTTP request
    """
    html = f"""\
        <html>
        <body>
            <h1>Request Line:</h1>
            <p>
                {method} {path} {http_version}
            </p>
            <h1>Headers:</h1>
            <pre>{pformat(request_header)}</pre>
            <h1>Body:</h1>
            <pre>{request_body.decode("utf-8", "ignore")}</pre>

        </body>
        </html>
    """
    response_body = textwrap.dedent(html).encode()

    # Content-Specify Type
    content_type = "text/html; charset=UTF-8"

    #Generate response line
    response_line = "HTTP/1.1 200 OK\r\n"

    return response_body, content_type, response_line


def parameters(
    method: str,
    request_body: bytes,
) -> Tuple[bytes, Optional[str], str]:
    """
Display HTML to display POST parameters
    """

    #Returns 405 for GET requests
    if method == "GET":
        response_body = b"<html><body><h1>405 Method Not Allowed</h1></body></html>"
        content_type = "text/html; charset=UTF-8"
        response_line = "HTTP/1.1 405 Method Not Allowed\r\n"

    elif method == "POST":
        post_params = urllib.parse.parse_qs(request_body.decode())
        html = f"""\
            <html>
            <body>
                <h1>Parameters:</h1>
                <pre>{pformat(post_params)}</pre>                        
            </body>
            </html>
        """
        response_body = textwrap.dedent(html).encode()

        # Content-Specify Type
        content_type = "text/html; charset=UTF-8"

        #Generate response line
        response_line = "HTTP/1.1 200 OK\r\n"

    return response_body, content_type, response_line

This is not particularly difficult either. I just brought in the exact process of dynamically generating the response that was originally written in workerthread.py.


It is good to cut out the views function, but as it is now, the number of arguments is different for each function, "The mantissa that processes this path needs the arguments of this and this, and the function that processes this path needs the arguments of that, that, and that ..." And so on, the caller must know the details of the caller.

In the world of programming, it is known that the source code becomes simple when one module is made so that the details of the other module are not known as much as possible.

Let's refactor a little more in the next STEP and realize that.

STEP2: Unify the interface of the views function

Now the WorkerThread class needs to know the details of the views function because it can't be called without knowing what and how many arguments it needs for each function.

An easy way to get rid of this situation is to ** "I don't know which parameter each function uses, but I'll pass it all anyway" **.

Source code

Let's actually take a look at the source code.

study/workerthread.py https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter16-2/workerthread.py

study/views.py https://github.com/bigen1925/introduction-to-web-application-with-python/blob/main/codes/chapter16-2/views.py

Commentary

study/views.py Let's start with views.py

Lines 8-14, 66-72

def now(
    method: str,
    path: str,
    http_version: str,
    request_header: dict,
    request_body: bytes,
) -> Tuple[bytes, Optional[str], str]:
def parameters(
    method: str,
    path: str,
    http_version: str,
    request_header: dict,
    request_body: bytes,
) -> Tuple[bytes, Optional[str], str]:

Arguments are unified in all view functions so that all request information can be received. These arguments are not used in the function, but by making them available, the caller does not have to think about "what is needed and what is not needed".

study/workerthread.py Next is the side that calls the view function.

Lines 29-34

    #Correspondence between path and view functions
    URL_VIEW = {
        "/now": views.now,
        "/show_request": views.show_request,
        "/parameters": views.parameters,
    }

The correspondence between the path and view functions is defined as a constant. It is a ** dictionary that has path as a key and ** view function corresponding to path as a value.

Column: Python functions are first-class citizens

Depending on the language, you may be surprised to "set a" function "as a dictionary (or associative array) value" or "assign a" function "to a variable" as described above.

But in python this is a legitimate treatment.

Objects that can be handled as values, such as assigning to variables and passing to operations and functions (as arguments and return values), are called ** first-class citizens **. In python ** all objects are first-class citizens **, and functions are no exception.

Therefore, it is also possible to assign a function to a variable or create a function that receives a function and returns a function.

The latter is known as "metaprogramming" and anyone interested should check it out.

Line 64-69

            #If there is a view function corresponding to path, get the function and call it to generate a response
            if path in self.URL_VIEW:
                view = self.URL_VIEW[path]
                response_body, content_type, response_line = view(
                    method, path, http_version, request_header, request_body
                )

path in self.URL_VIEW checks to see if the dictionary key self.URL_VIEW contains path. In other words, we are checking if the view function corresponding to path is registered.

If it has been registered, the value of the dictionary corresponding to that key is acquired and assigned to the variable view. That is, the variable view is assigned the ** view function ** (rather than the return value of calling the view function).

In the last line, view (~~) is used to call the function assigned to the variable view and get the return value.


It's worth noting that ** all view functions now take the same arguments (method, path, http_version, request_header, request_body), which abstracts the view function. ** **

Previously, the arguments were different for each function, so even if you said "call the view function", you couldn't call it correctly unless you knew "what kind of function the function is". However, by unifying the arguments (= unifying the interface), ** "I don't know what the function is, but I can call it anyway" **.

This eliminates the need for if branching according to path (or function) within workerthread.

Column: Abstraction

In this way, "it is possible to avoid having to deal with concrete things by extracting only some of the common properties from concrete things" is called ** abstraction **. , It is a very important technique in programming.

In this case, by unifying the interface from concrete functions such as now () show_rewuest () parameters "Takes 5 arguments method, path, http_version, request_header, request_body and returns 2 values response_body, response_line" By extracting (= abstracting) only the property, the caller "I don't know how many functions it is, but I call it with 5 arguments." It means that it can be handled like this.

Or you could say "unified interface for abstraction".

STEP3: Simplify the interface of the view function

It's good that the interface of the view function is standardized and the caller's view is better, but there are many five arguments.

The fact that an HTTP request has a lot of information is an unavoidable fact, but it's awkward to have it distributed and stored in disparate variables.

So, let's create a class that expresses the HTTP request and put the information together there.

This also simplifies the interface of the view function.


Continue with Book!

Chapter "Create Request / Response / View class to improve visibility"

Recommended Posts

Updated "Introduction to Python Web Application Homebrew for Slow 3rd Year Web Engineers"
Updated "Introduction to Python Web Application Homebrew for Slow 3rd Year Web Engineers"
Updated "Introduction to Python Web Application Homebrew for Slow 3rd Year Web Engineers"
Updated "Introduction to Python Web Application Homebrew for Slow 3rd Year Web Engineers"
Updated "Introduction to Python Web Application Homebrew for Slow 3rd Year Web Engineers"
Updated "Introduction to Python Web Application Homebrew for Slow 3rd Year Web Engineers"
[Introduction to Udemy Python3 + Application] 43. for else statement
Introduction to Python For, While
[Introduction to Udemy Python3 + Application] 42. for statement, break statement, and continue statement
[Introduction to Udemy Python 3 + Application] 58. Lambda
[Introduction to Udemy Python 3 + Application] 31. Comments
[Introduction to Udemy Python 3 + Application] 57. Decorator
[Introduction to Udemy Python 3 + Application] 56. Closure
[Introduction to Udemy Python3 + Application] 59. Generator
[Introduction to Udemy Python 3 + Application] Summary
An introduction to Python for non-engineers
Easy-to-understand explanation of Python Web application (Django) even for beginners (5) [Introduction to DB operation with Django shell]
[Introduction to Udemy Python3 + Application] 18. List methods
[Introduction to Udemy Python3 + Application] 63. Generator comprehension
[Introduction to Udemy Python3 + Application] 28. Collective type
[Introduction to Udemy Python3 + Application] 33. if statement
[Introduction to Udemy Python3 + Application] 13. Character method
[Introduction to Udemy Python3 + Application] 55. In-function functions
[Introduction to Udemy Python3 + Application] 48. Function definition
[Introduction to Udemy Python 3 + Application] 10. Numerical values
[Introduction to Udemy Python3 + Application] 21. Tuple type
[Introduction to Udemy Python3 + Application] 45. enumerate function
[Introduction to Udemy Python3 + Application] 41. Input function
[Introduction to Udemy Python3 + Application] 17. List operation
[Introduction to Udemy Python3 + Application] 65. Exception handling
[Introduction to Udemy Python3 + Application] 11. Character strings
[Introduction to Udemy Python3 + Application] 44. range function
[Introduction to Udemy Python3 + Application] 46. Zip function
[Introduction to Udemy Python3 + Application] 24. Dictionary type
[Python] Web application design for machine learning
An introduction to Python for machine learning
[Introduction to Udemy Python3 + Application] 8. Variable declaration
[Introduction to Udemy Python3 + Application] 29. Set method
[Introduction to Udemy Python3 + Application] 16. List type
[Introduction to Udemy Python3 + Application] 61. Dictionary comprehension
[Introduction to Udemy Python 3 + Application] 22. Tuple unpacking
An introduction to Python for C programmers
[Introduction to Udemy Python3 + Application] 47. Process the dictionary with a for statement
Take the free "Introduction to Python for Machine Learning" online until 4/27 application
An introduction to self-made Python web applications for a sluggish third-year web engineer
Easy-to-understand explanation of Python web application (Django) even for beginners (4) [Routing settings / Introduction to MTV design patterns]
Updated to Python 2.7.9
[Introduction to Udemy Python 3 + Application] 26. Copy of dictionary
[Introduction to Udemy Python3 + Application] 23. How to use tuples
[Introduction to Udemy Python3 + Application] 60. List comprehension notation
[Introduction to Udemy Python 3 + Application] 19. Copy of list
[Introduction to Udemy Python 3 + Application] 38. When judging None
Introduction to Tornado (1): Python web framework started with Tornado
[Introduction to Udemy Python3 + Application] 40.while else statement
[Introduction to Udemy Python3 + Application] 62. Set comprehension notation
Steps to develop a web application in Python
[Introduction to Udemy Python3 + Application] 64. Namespace and Scope
[Introduction to Python3 Day 20] Chapter 9 Unraveling the Web (9.1-9.4)
[Introduction to Udemy Python3 + Application] 67. Command line arguments
[Introduction to Udemy Python3 + Application] 9. First, print with print
Introduction to Programming (Python) TA Tendency for beginners