Python Falcon 简明教程

Python Falcon - Jinja2 Template

Falcon 库主要用于构建 API 和微服务。因此,默认情况下,Falcon 响应器返回 JSON 响应。但是,如果将内容类型更改为 falcon.MEDIA_HTML, ,则可以呈现 HTML 输出。

The Falcon library is primarily used to build APIs and microservices. Hence, by default, a Falcon responder returns a JSON response. However, if the content type is changed to falcon.MEDIA_HTML, it is possible to render HTML output.

使用变量数据呈现 HTML 内容非常繁琐。为此,需要使用网络模板库。许多 Python Web 框架都捆绑了特定的模板库。但 Falcon 是一个极简主义微框架,不会捆绑任何模版库。

Rendering a HTML content with variable data is very tedious. For this purpose, web templating libraries are used. Many Python web frameworks are bundled with specific template library. But Falcon being a minimalist micro framework doesn’t come pre-bundled with anyone.

Jinja2 是许多 Python 框架使用的一个最流行的模板库。在本节中,我们将看到如何在 Falcon 应用中使用 inja2。jinja2 是一种快速且对设计人员友好的模板语言,易于配置和调试。它的沙盒环境便于阻止执行不可信代码、禁止潜在不安全数据,并防止跨站点脚本攻击(称为 XSS attacks )。

Jinja2 is one of the most popular template libraries used by many python frameworks. In this section, we shall see how to use inja2 with Falcon application. The jinja2 is a fast and designer-friendly templating language that is easy to configure and debug. Its sandboxed environment makes it easy to prevent the execution of untrusted code, prohibit potentially unsafe data, and prevent cross-site scripting attacks (called XSS attacks).

jinja2 的另一个非常强大的功能是 template inheritance ,其中您可以定义一个具有通用设计功能的基本模板,而子模板可以覆盖该基本模板。

Another very powerful feature of jinja2 is the template inheritance, wherein You can define a base template having common design features which child templates can override.

首先,使用 PIP 实用程序在当前 Python 环境中安装 jinja2

First of all, install jinja2 in the current Python environment with the use of PIP utility.

pip3 install jinja2

Hello World Template

jinja2 模块定义了 Template 类。Template 对象是通过读取包含 HTML 脚本(带 .html 扩展名)的文件内容来获取的。通过调用这个 Template 对象的 render() 方法,可以向客户端浏览器呈现 HTML 响应。Response 对象的 content_type 属性必须设置为 falcon.MEDIA_HTML

The jinja2 module defines a Template class. A Template object is obtained by reading the contents of a file containing HTML script (one with .html extension). By invoking the render() method of this Template object, HTML response can be rendered to the client browser. The content_type property of Response object must be set to falcon.MEDIA_HTML.

让我们将以下 HTML 脚本另存为 hello.py 放置在应用文件夹中。

Let us save the following HTML script as hello.py in the application folder.

<html>
   <body>
      <h2>Hello World</h2>
   </body>
</html>

Example

下面的资源类中的 on_get() 响应器读取这个文件并将其呈现为 HTML 响应。

The on_get() responder in the resource class below reads this file and renders it as HTML response.

import uvicorn
import falcon
import falcon.asgi
from jinja2 import Template
class HelloResource:
   async def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("hello.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()
app = falcon.asgi.App()
hello = HelloResource()
app.add_route('/hello', hello)
if __name__ == "__main__":
   uvicorn.run("hello:app", host="0.0.0.0", port=8000, reload=True)

Output

运行以上 Python 代码并访问浏览器中的 http://localhost:8000/hello 链接。

Run the above Python code and visit http://localhost:8000/hello link in the browser.

jinja2

Template Variable

jinja2 是一个服务器端模板库。网页通过将 jinja2 模板语言的各种元素作为占位符放置到 HTML 脚本内的适当定界符中来构造为模板。模板引擎读取 HTML 脚本,在服务器上用上下文数据替换占位符,重新组装 HTML 并将其呈现给客户端。

jinja2 is a server-side templating library. The web page is constructed as a template by putting various elements of jinja2 templating language as place-holders within appropriate delimiters inside the HTML script. The template engine reads the HTML script, substitutes the place-holders with context data on the server, reassembles the HTML, and renders it to the client.

Template.render() 函数有一个可选的上下文字典参数。这个字典的关键属性变成模板变量。这有助于呈现响应器在网页中传递的数据。

The Template.render() function has an optional context dictionary parameter. The key attributes of this dictionary become the template variables. This helps in rendering the data passed by the responders in the web page.

Example

在以下示例中,路由 /hello/nm 已使用路径参数 nm 注册了资源对象。 on_get() 响应器将其作为上下文传递给从网页获取的模板对象。

In the following example, the route /hello/nm is registered with the resource object, where nm is the path parameter. The on_get() responder passes it as a context to the template object obtained from a web page.

import uvicorn
import falcon
import falcon.asgi
from jinja2 import Template
class HelloResource:
   async def on_get(self, req, resp, nm):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("hello.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'name':nm})
app = falcon.asgi.App()
hello = HelloResource()
app.add_route('/hello/{nm}', hello)
if __name__ == "__main__":
   uvicorn.run("hello:app", host="0.0.0.0", port=8000, reload=True)

hello.html 在模板变量名称中读取路径参数。它充当 HTML 脚本中的占位符。它被放置在 {{}} 符号中,以便其值显示为 HTML 响应。

The hello.html reads the path parameter in a template variable name. It acts as a place holder in the HTML script. It is put in {{ and }} symbols so that its value appears as a HTML response.

<html>
   <body>
      <h2>Hello {{ name }}</h2>
   </body>
</html>

Output

运行 Python 代码并输入 http://localhost:8000/hello/Priya 作为 URL。浏览器将显示以下输出:

Run the Python code and enter http://localhost:8000/hello/Priya as the URL. The browser displays the following output −

jinja2 hello

Loop in jinja2 Template

如果响应器传递任何 Python 可迭代对象,例如列表、元组或字典,则可以使用其循环构造语法在 jinja2 模板内部遍历其元素。

If the responder passes any Python iterable object such as a list, tuple or a dictionary, its elements can be traversed inside the jinja2 template using its looping construct syntax.

{% for item in collection %}
HTML block
{% endfor %}

在以下示例中, on_get() 响应器向模板 list.html 发送 students 对象,该对象是 dict 对象的列表。它会遍历数据并将其呈现为 HTML 表格。

In the following example, the on_get() responder sends students object which is a list of dict objects, to the template list.html. It in turn traverses the data and renders it as a HTML table.

import falcon
import json
from waitress import serve
from jinja2 import Template
students = [
   {"id": 1, "name": "Ravi", "percent": 75.50},
   {"id": 2, "name": "Mona", "percent": 80.00},
   {"id": 3, "name": "Mathews", "percent": 65.25},
]
class StudentResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_HTML
      fp=open("list.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'students':students})

list.html 是一个 jinja2 模板。它接收 students 对象作为字典对象列表,并将每个键的值放在表的 <td>..<.td> 元素内部。

list.html is a jinja2 template. It receives the students object as list of dictionary objects and puts the value of each key inside <td>..<.td> element of a table.

<html>
<body>
<table border=1>
   <thead> <tr>
      <th>Student ID</th> <th>Student Name</th>
      <th>percentage</th>
      <th>Actions</th>
   </tr> </thead>
   <tbody>
   {% for Student in students %}
   <tr> <td>{{ Student.id }}</td> <td>{{ Student.name }}</td>
      <td>{{ Student.percent }}</td>
      <td>
         <a href="#">Edit</a>
         <a href="#">Delete</a>
      </td> </tr>
   {% endfor %}
   </tbody>
</table>
</body>
</html>

访问浏览器地址栏中的 /students 路由。学生的列表将在浏览器中呈现。

Visit the /students route in the browser’s address bar. The list of students is rendered in the browser.

jinja2 img

HTML Form Template

在此部分,我们将看到 Falcon 如何从 HTML 表单读取数据。让我们将以下 HTML 脚本保存为 myform.html。我们将使用它来获取 Template 对象并呈现它。

In this section, we shall see how Falcon reads the data from HTML form. Let us save the following HTML script as myform.html. We shall use it for obtaining Template object and render it.

<html>
<body>
   <form method="POST" action="http://localhost:8000/students">
   <p>Student Id: <input type="text" name="id"/> </p>
   <p>student Name: <input type="text" name="name"/> </p>
   <p>Percentage: <input type="text" name="percent"/> </p>
   <p><input type="submit"> </p>
</body>
</html>

Falcon App 对象在 Hello.py 文件中声明,其中还将资源类映射到 /adddnew 路由。 on_get() 响应者读取 myform.html 并对它进行呈现。HTML 表单将会显示出来。通过 POST 方法将表单提交到 /students 路由。

The Falcon App object is declared in Hello.py file which also has a resource class mapped to /adddnew route. The on_get() responder reads the myform.html and renders the same. The HTML form will be displayed. The form is submitted to /students route by POST method.

要能够读取表单数据,必须将 auto_parse_form_urlencoded 类的 falcon.RequestOptions 属性设置为 True。

To be able to read the form data, the auto_parse_form_urlencoded property of falcon.RequestOptions class must be set to True.

app = falcon.App()
app.req_options.auto_parse_form_urlencoded = True

此处,我们还从 student.py 中导入 StudentResource 类。 on_get() 响应器呈现学生列表。

Here, we also import StudentResource class from student.py. The on_get() responder renders the list of students.

当用户填写并提交表单时,将调用 on_post() 响应器。此方法收集表单数据到 req.params 属性中,它只不过是一个表单元素及其值的字典。然后附加 students 字典。

The on_post() responder will be called when the user fills and submits the form. This method collects the form data in the req.params property, which is nothing but a dictionary of form elements and their values. The students dictionary is then appended.

def on_post(self, req, resp):
   student=req.params
   students.append(student)

hello.py 的完整代码如下 −

The complete code of hello.py is as follows −

import falcon
import json
from waitress import serve
from jinja2 import Template
from student import StudentResource
class MyResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("myform.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()
app = falcon.App()
app.req_options.auto_parse_form_urlencoded = True
form = MyResource()
app.add_route('/addnew', form)
app.add_route("/students", StudentResource())
if __name__ == '__main__':
   serve(app, host='0.0.0.0', port=8000)

具有 StudentResource 类和 on_get()on_post() 响应器的 student.py 如下 −

The student.py having StudentResource class and on_get() and on_post() responders is as follows −

import falcon
import json
from waitress import serve
from jinja2 import Template
students = [
   {"id": 1, "name": "Ravi", "percent": 75.50},
   {"id": 2, "name": "Mona", "percent": 80.00},
   {"id": 3, "name": "Mathews", "percent": 65.25},
]
class StudentResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_HTML
      fp=open("list.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render({'students':students})

   def on_post(self, req, resp):
      student = req.params
      students.append(student)
      resp.text = "Student added successfully."
      resp.status = falcon.HTTP_OK
      resp.content_type = falcon.MEDIA_JSON

从命令行运行 hello.py 。通过输入 http://locLhost:8000/addnew 在浏览器中打开 HTML 表单。

Run hello.py from the command line. Open the HTML form in the browser by entering http://locLhost:8000/addnew.

jinja host

将附加 students 数据库字典。访问 /students 路由。你会发现附加了新行。

The students database dictionary will be appended. Visit /students route. You will find a new row appended.

jinja2 example

Multipart Forms

为了让用户从本地文件系统选择文件,必须将 HTML 表单的 enctype 属性设置为 multipart/form-data。Falcon 使用 MultipartFormHandler 处理 multipart/form-data 媒体类型,允许其迭代表单中的主体部分。

In order to let the user select files from the local filesystem, the enctype attribute of HTML form must be set to multipart/form-data. Falcon uses MultipartFormHandler to handle the multipart/form-data media type, allowing it to iterate over the body parts in the form.

BodyPart 类具有以下属性 −

The BodyPart class has the following properties −

  1. stream − stream wrapper just for the current body part

  2. data − body part content bytes

  3. content_type would default to text/plain if not specified, as per RFC

  4. text − the current body part decoded as text string (only provided it is of type text/plain, None otherwise)

  5. media − automatically parsed by media handlers in the same way as req.media

  6. name, filename − relevant parts from the Content-Disposition header

  7. secure_filename − sanitized filename that could safely be used on the server filesystem.

以下 HTML 脚本 ( index.html ) 是一个多部分表单。

The following HTML script (index.html) is a multi-part form.

<html>
   <body>
      <form action="http://localhost:8000/hello" method="POST" enctype="multipart/form-data">
         <h3>Enter User name</h3>
         <p><input type='text' name='name'/></p>
         <h3>Enter address</h3>
         <p><input type='text' name='addr'/></p>
         <p><input type="file" name="file" /></p>
         <p><input type='submit' value='submit'/></p>
      </form>
   </body>
</html>

此表格由下方代码中的 on_get() 响应器渲染。将表格数据提交给 on_post() 方法,该方法将迭代部分并发送表格数据的 JSON 响应。

This form is rendered by the on_get() responder of the HelloResource class in the code below. The form data is submitted to on_post() method which iterates over the parts and sends a JSON response of the form data.

import waitress
import falcon
import json
from jinja2 import Template
class HelloResource:
   def on_get(self, req, resp):
      resp.status = falcon.HTTP_200
      resp.content_type = 'text/html'
      fp=open("index.html","r")
      tempobj=Template(fp.read())
      resp.body=tempobj.render()

   def on_post(self, req, resp):
      result=[]
      for part in req.media:
         data={"name" :part.name,
            "content type":part.content_type,
            "value":part.text, "file":part.filename}
         result.append(data)
         resp.text = json.dumps(result)
         resp.status = falcon.HTTP_OK
         resp.content_type = falcon.MEDIA_JSON
app = falcon.App()
hello = HelloResource()
app.add_route('/hello', hello)
if __name__ == '__main__':
   waitress.serve(app, host='0.0.0.0', port=8000)

运行以上程序并访问 http://localhost:8000/hello 链接,如以下所示,以渲染表格 -

Run the above program and visit http://localhost:8000/hello link to render the form as shown below −

jinja2 user

当在填写数据后提交表格时,将在浏览器中显示 JSON 响应,如下所示:

When the form is submitted after filling the data, the JSON response is rendered in the browser as shown below −

[
   {
      "name": "name",
      "content type": "text/plain",
      "value": "SuyashKumar Khanna",
      "file": null
   },
   {
      "name": "addr",
      "content type": "text/plain",
      "value": "New Delhi",
      "file": null
   },
   {
      "name": "file",
      "content type": "image/png",
      "value": null,
      "file": "hello.png"
   }
]