Python Pyramid 简明教程

Python Pyramid - Quick Guide

Python Pyramid - Overview

Pyramid 是用 Python 编写的开源、符合 WSGI 的 Web 框架。最初将该项目命名为 Pylons,但后来以新名称 Pyramid 发行。

Pyramid is an open source, WSGI compliant web framework written in Python. Initially the project named as Pylons, but later released under the new name Pyramid.

  1. Pyramid is a minimalistic web framework. It doesn’t come packaged with any templating library or doesn’t have support for any specific database packages.

  2. However, it can be integrated both with SQL databases via SQLAlchemy and with the Zope Object Database, as well as other NoSQL databases such as CouchDB.

  3. Pyramid can also be configured to work with templating libraries such as Mako, Jinja2 or Chameleon.

  4. Pyramid has been developed by Chris McDonough. The first version of Pyramid was released in January 2011. The latest version, Pyramid 2.0 has been released in March 2021.

Comparison with Other Python Frameworks

Pyramid Web 应用框架受到 Zope 和 Django 框架的启发。因此,它结合了两者的最佳条款。

Pyramid web application framework is inspired by Zope and Django frameworks. As a result, it combines the best provisions of the two.

  1. Pyramid is largely based on repose.bfg framework. After it was merged with the Pylons project, the same was renamed as Pyramid in 2010.

  2. The ability to extend Pyramid application is borrowed from Zope library. Without modifying the application code, the application can be reused, modified or extended. The features such as declarative security layer and traversal of routes is inherited from Zope.

  3. As is the case of Pylons 1.0, Pyramid doesn’t enforce any policy. It also lets the user choose any database or templating system The URL dispatch approach is also inspired by Pylons.

  4. The concept of views is based on similar approach of Django. Extensive documentation is also a Django features adapted by Pyramid.

  5. Although the definition doesn’t fit exactly, Pyramid can be said to follow MVC (Model-View-Controller) approach.

Python Pyramid - Environment Setup

建议在已安装 Python 3.6 或更高版本的系统中安装 Pyramid 包。Pyramid 可安装在 Linux、MacOS 和 Windows 平台。安装它的最简单方法是使用 PIP 安装程序,最好在 Python 虚拟环境中进行。

It is recommended that the Pyramid package be installed on a system having Python 3.6 or above version installed. Pyramid can be installed on Linux, MacOS as well as Windows platform. Simplest way of installing it is by using PIP installer, preferably under a Python virtual environment.

pip3 install pyramid

尽管可以使用内置的 WSGI 开发服务器( wsgiref 模块的一部分)运行 Pyramid Web 应用程序,但不建议在生产环境中使用。因此,我们还安装了 Waitress,这是一款生产质量的纯 Python WSGI 服务器(也是 Pylons 项目的一部分)。

Although a Pyramid web application can be run using the built-in WSGI development server that is a part of the wsgiref module, it is not recommended for use in production environment. Hence, we also install Waitress, a production-quality pure-Python WSGI server (also a part of Pylons project).

pip3 install waitress

这将安装 Pyramid(版本 2.0)、Waitress(版本 2.1.2),以及 Pylon 项目的其他依赖项,例如 WebOb、PasteDeploy 等。要检查安装了哪些内容,请运行 pip freeze 命令。

This will install Pyramid (ver 2.0), Waitress (ver 2.1.2) in addition to other dependencies from Pylon project such that WebOb, PasteDeploy, and others. To check what gets installed, run pip freeze command.

pip3 freeze
hupper==1.10.3
PasteDeploy==2.1.1
plaster==1.0
plaster-pastedeploy==0.7
pyramid==2.0
translationstring==1.4
venusian==3.0.0
waitress==2.1.2
WebOb==1.8.7
zope.deprecation==4.4.0
zope.interface==5.4.0

Python Pyramid - Hello World

Example

要检查 Pyramid 及其依赖项是否正确安装,请输入以下代码并使用任何支持 Python 的编辑器将其另存为 hello.py

To check whether Pyramid along with its dependencies are properly installed, enter the following code and save it as hello.py, using any Python-aware editor.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
   return Response('Hello World!')

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('hello', '/')
      config.add_view(hello_world, route_name='hello')
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

需要 Configurator 对象来定义 URL 路由并向其绑定视图函数。可从该 config 对象获取 WSGI 应用程序对象,该对象作为 make_server() 函数的参数,以及本地主机的 IP 地址和端口。当调用 serve_forever() 方法时,服务器对象会进入侦听循环。

The Configurator object is required to define the URL route and bind a view function to it. The WSGI application object is obtained from this config object is an argument to the make_server() function along with the IP address and port of localhost. The server object enters a listening loop when serve_forever() method is called.

在命令终端中以 as 运行该程序。

Run this program from the command terminal as.

Python hello.py

Output

WSGI 服务器开始运行。打开浏览器,并在地址栏中输入 [role="bare"] [role="bare"]http://loccalhost:6543/ 。当请求得到接受时, hello_world() 视图函数得到执行。它返回“Hello world”消息。“Hello world”消息将显示在浏览器窗口中。

The WSGI server starts running. Open the browser and enter [role="bare"]http://loccalhost:6543/ in the address bar. When the request is accepted, the hello_world() view function gets executed. It returns the Hello world message. The Hello world message will be seen in the browser window.

hello world

如同前面所述, wsgiref 模块中的 make_server() 函数创建的开发服务器不适合用于生产环境。因此,我们应当使用 Waitress 服务器。根据以下代码修改 hello.py −

As mentioned earlier, the development server created by make_server() function in the wsgiref module is not suited for production environment. Instead, we shall use Waitress server. Modify the hello.py as per the following code −

from pyramid.config import Configurator
from pyramid.response import Response
from waitress import serve

def hello_world(request):
   return Response('Hello World!')

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('hello', '/')
      config.add_view(hello_world, route_name='hello')
      app = config.make_wsgi_app()
      serve(app, host='0.0.0.0', port=6543)

所有其他功能都是相同的,不同之处在于我们使用 waitress 模块的 serve() 函数来启动 WSGI 服务器。在运行该程序后,访问浏览器中的 '/' 路由,会像之前一样显示“Hello world”消息。

All other functionality is same, except we use serve() function of waitress module to start the WSGI server. On visiting the '/' route in the browser after running the program, the Hello world message is displayed as before.

除了函数,还可以使用可调用的类作为视图。一个可调用的类是覆盖了 call() 方法的类。

Instead of a function, a callable class can also be used as a view. A callable class is the one which overrides the call() method.

from pyramid.response import Response
class MyView(object):
   def __init__(self, request):
      self.request = request
   def __call__(self):
      return Response('hello world')

Python Pyramid - Application Configuration

Pyramid 应用程序对象拥有一个应用程序注册表,用于存储视图函数到路由的映射,以及其他特定于应用程序的组件注册。Configurator 类用于构建应用程序注册表。

The Pyramid application object has an application registry that stores mappings of view functions to routes, and other application-specific component registrations. The Configurator class is used to build the application registry.

Configurator 生命周期由上下文管理器管理,它返回一个应用程序对象。

The Configurator life cycle is managed by a context manager that returns an application object.

with Configurator(settings=settings) as config:
   #configuration methods
   app = config.make_wsgi_app()

Configurator 类定义了下列重要方法,用于自定义应用程序 −

The Configurator class defines the following important methods to customize the application −

add_route()

此方法注册一个路由用于 URL 派发。使用以下参数 −

This method registers a route for URL dispatch. Following arguments are used −

  1. name − The first required positional argument must be a unique name for the route. The name is used to identify the route when registering views or generating URLs.

  2. pattern − The second required positional argument is a string representing the URL path optionally containing variable placeholders for parsing the variable data from the URL. The placeholders are surrounded by curly brackets. For example, "/students/{id}".

  3. request_method − The value can be one of "GET", "POST", "HEAD", "DELETE", "PUT". Requests only of this type will be matched against the route.

add_view()

此方法会向应用程序注册表添加视图配置。它将一个视图函数绑定到配置中的 route_name 。需要的参数有:

This method adds a view configuration to the application registry. It binds a view function to the route_name present in the configuration. The arguments required are −

  1. view − The name of a view function.

  2. route_name − A string that must match the name of a route configuration declaration.

  3. request_method − Either a string (such as "GET", "POST", "PUT", "DELETE", "HEAD" or "OPTIONS") representing an HTTP REQUEST_METHOD, or a tuple containing one or more of these strings.

add_static_view()

此方法会添加一个 view 用于呈现静态资产(如图片和 CSS 文件),并使用以下参数:

This method adds a view used to render static assets such as images and CSS files, and uses the following arguments −

  1. name − This argument is a string representing an application-relative local URL prefix, or a full URL.

  2. Path − This argument represents the path on disk where the static files reside. Its value can be an absolute or a package-relative path.

此方法进而调用 Configurator 对象的 add_route() 方法。

This method in turn calls the add_route() method of Configurator object.

add_notfound_view()

该方法添加一个视图,当视图查找程序在当前请求中找不到匹配的视图时执行。以下代码显示了一个示例:

This method adds a view to be executed when a matching view cannot be found for the current request. The following code shows an example −

from pyramid.config import Configurator
from pyramid.response import Response

def notfound(request):
   return Response('Not Found', status='404 Not Found')

config.add_notfound_view(notfound)

add_forbidden_view()

配置应用程序注册表,以定义在出现 HTTPForbidden 异常时执行的视图。参数列表包含对返回 403 状态响应的函数的引用。如果没有提供参数,则注册表会添加 default_exceptionresponse_view().

Configures the application registry so as to define a view to be executed when there is HTTPForbidden exception raised. The argument list contains a reference to a function that returns a 403 status response. If no argument is provided, the registry adds default_exceptionresponse_view().

add_exception_view()

此方法导致为指定异常向配置中添加异常视图函数。

This method causes addition of an exception view function to the configuration, for the specified exception.

make_wsgi_app()

此方法返回 Pyramid WSGI 应用程序对象。

This method returns a Pyramid WSGI application object.

scan()

此包装器用于注册视图。它导入所有应用程序模块,寻找 @view_config 装饰器。

This is a wrapper for registering views. It imports all application modules looking for @view_config decorators.

对于每个装饰器,它使用相同的关键字参数调用 config.add_view(view)。调用 scan() 函数会扫描包和所有子包中的所有装饰。

For each one, it calls config.add_view(view) with the same keyword arguments. A call to scan() function performs the scan of the package and all the subpackages for all the decorations.

执行应用程序注册表配置的一系列典型语句如下代码段所示:

A typical sequence of statements that performs configuration of application registry is as in the following code snippet −

from pyramid.config import Configurator

with Configurator() as config:
   config.add_route('hello', '/')
   config.add_view(hello_world, route_name='hello')
   app = config.make_wsgi_app()

这种应用程序配置方法被称为命令式配置。Pyramid 提供了另一种配置方法,称为装饰配置。

This approach towards configuration of the application is called imperative configuration. Pyramid provides another approach towards configuration, called as decorative configuration.

Declarative Configuration

有时,通过命令式代码进行配置变得非常困难,尤其在应用代码分散在许多文件中时。声明式配置是一种便捷的方法。 pyramid.view 模型定义了 view_config - 一个函数、类或方法修饰器,该修饰器允许在非常接近视图函数自身定义的位置注册视图。

Sometimes, it becomes difficult to do the configuration by imperative code, especially when the application code is spread across many files. The declarative configuration is a convenient approach. The pyramid.view model defines view_config – a function, class or method decorator - that allows the view registrations very close to the definition of view function itself.

@view_config() 修饰器提供了两个重要参数。它们是 route_namerequest_method 。它们与 Configurator 类的 add_route() 方法中给出的说明相同。紧跟在它下面的函数获得了修饰,以便将它绑定到添加到应用对象的注册表中的路由。

Two important arguments are provided to @view_config() decorator. They are route_name and request_method. They bear same explanation as in add_route() method of Configurator class. The function just below it is decorated so that it is bound to the route added to the registry of the application object.

以下是 hello_world() 视图函数声明式配置示例:

Give below is the example of declarative configuration of hello_world() view function −

from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='hello', request_method='GET')
def hello_world(request):
   return Response('Hello World!')

view_config 修饰器向 hello_world() 函数添加了一个属性,使其可供扫描在之后找到它。

The view_config decorator adds an attribute to the hello_world() function, making it available for a scan to find it later.

Example

配置修饰和扫描调用共同称为声明式配置。以下代码使用声明式方法配置应用注册表。

The combination of configuration decoration and the invocation of a scan is collectively known as declarative configuration. Following code configures the application registry with declarative approach.

scan() 函数发现路由及其映射的视图,因此需要添加命令式配置语句。

The scan() function discovers the routes and their mapped views, so that there is the need to add imperative configuration statements.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='hello', request_method='GET')
def hello_world(request):
   return Response('Hello World!')

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('hello', '/')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

扫描器将 view_config 的参数翻译为对 pyramid.config.Configurator.add_view() 方法的调用,因此,该操作等同于以下语句:

The scanner translates the arguments to view_config into a call to the pyramid.config.Configurator.add_view() method, so that the action is equivalent to the following statement −

config.add_view(hello_world, route_name='hello', request_method='GET')

Output

运行上述程序之后,WSGI 服务器启动。当浏览器访问链接 [role="bare"] [role="bare"]http://localhost:6543/ 时,依然会渲染“Hello World”信息。

After the above program is run, the WSGI server starts. When the browser visits the link [role="bare"]http://localhost:6543/, the "Hello World" message is rendered as before.

config

Python Pyramid - Url Routing

在 MVC 架构出现之前,Web 应用使用一种机制:将用户在浏览器中输入的 URL 映射到程序文件,该程序文件的输出会作为对浏览器的响应渲染为 HTML。Pyramid 框架使用一种路由机制:在应用的注册表中,URL 的端点与不同的 URL 模式匹配,调用其映射的视图并渲染响应。

Before the advent of MVC architecture, web applications used the mechanism of mapping the URL entered by the user in the browser, to a program file whose output was rendered as HTML to as a response back to the browser. Pyramid framework uses a routing mechanism where the endpoint of the URL is matched with different URL patterns registered in the application’s registry, invokes its mapped view and renders the response.

典型的 URL 包含三个部分:协议(例如 http:// 或 https://),后面是 IP 地址或主机名。主机名之后,URL 的剩余部分称为路径或端点。

A typical URL comprises of three parts: The protocol (such as http:// or https://) followed by the IP address or hostname. The remaining part of the URL after first / after the hostname is called as the path or endpoint.

mysite

端点后面跟着一个或多个可变部分,形成路由。可变部分标识符用大括号括起来。例如,对于上述 URL,路由是 /blog/{id}

The endpoint followed by one or more variable parts forms the route. The variable part identifiers are surrounded by curly brackets. For example, for the above URL, the route is /blog/{id}

WSGI 应用充当路由器。它针对路由图中存在的 URL 模式检查传入请求。如果发现匹配,则执行与其关联的视图可调用对象,并返回响应。

The WSGI application acts as a router. It checks the incoming request against the URL patterns present in the route map. If a match is found, its associated view callable is executed and the response is returned.

Route Configuration

通过调用 Configurator 对象的 add_route() 方法,会向该应用添加一个新路由。路由有名称,该名称用作用于生成 URL 的标识符,还有一个模式,该模式用于与 URL 的 PATH_INFO 部分(方案和端口后面的部分,例如 URL http://example.com/blog/1). 中的 /blog/1)进行匹配。

A new route is added to the application by invoking add_route() method of the Configurator object. A route has a name, which acts as an identifier to be used for URL generation and a pattern that is meant to match against the PATH_INFO portion of a URL (the portion following the scheme and port, e.g., /blog/1 in the URL http://example.com/blog/1).

如前所述,add_route() 方法的 pattern 参数可以有一个或多个占位符标识符,这些标识符用大括号括起来,并用 / 分隔。以下语句将“index”指定为提供给 '/{name}/{age}' 模式的路由名称。

As mentioned earlier, the pattern parameter of add_route() method can have one or more placeholder identifiers surrounded by curly brackets and separated by /. Following statement assigns 'index' as the name of route given to '/{name}/{age}' pattern.

config.add_route('index', '/{name}/{age}')

为将视图可调用对象与该路由关联起来,我们如下使用 add_view() 函数:

To associate a view callable to this route, we use add_view() function as follows −

config.add_view(index, route_name='index')

index() 函数应该可用,以便将路由与它匹配。

The index() function should be available for the route to be matched to it.

def index(request):
   return Response('Root Configuration Example')

Example

我们在以下程序中放入这些语句:

We put these statements in the program below −

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

def index(request):
   return Response('Root Configuration Example')

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('index', '/{name}/{age}')
      config.add_view(index, route_name='index')
      app = config.make_wsgi_app()
server = make_server('0.0.0.0', 6543, app)
server.serve_forever()

Output

运行上述代码,在浏览器中访问 http://localhost:6543/Ravi/21 。由于该 URL 的 PATH_INFO 与 index 路由匹配,因此显示以下输出:

Run the above code and visit http://localhost:6543/Ravi/21 in the browser. As the URL’s PATH_INFO matches with the index route, the following output is displayed −

root configuration

路由配置中使用的模式通常以正斜杠 (/) 字符开头。模式段(模式中 / 字符之间的单个项目)可以是字符串文字、占位符标记(例如,{name}),或二者的某种组合。替换标记无需以 / 字符开头。

The pattern used in route configuration usually starts with a forward slash (/) character. A pattern segment (an individual item between / characters in the pattern) may either be a literal string, or it may be a placeholder marker (e.g., {name}), or a certain combination of both. A replacement marker does not need to be preceded by a / character.

以下是路由模式的一些示例

Here are some examples of route patterns

/student/{name}/{marks}
/{id}/student/{name}/{marks}
/customer/{id}/item/{itemno}
/{name}/{age}

占位符标识符必须是有效的 Python 标识符。因此,它必须以大写或小写 ASCII 字母或下划线开头,且只能包含大写或小写 ASCII 字母、下划线和数字。

The place holder identifier must be a valid Python identifier. Hence, it must begin with an uppercase or lowercase ASCII letter or an underscore, and it can only have uppercase or lowercase ASCII letters, underscores, and numbers.

Route Matching

当传入请求与与特定路由配置关联的 URL 模式匹配时,一个名为 matchdict 的字典对象作为请求对象的属性添加。

When the incoming request matches with the URL pattern associated with a particular route configuration, a dictionary object named matchdict is added as an attribute of the request object.

request.matchdict 包含匹配模式元素中替换模式的值。 matchdict 中的键为字符串,而其值为 Unicode 对象。

The request.matchdict contains the values that match replacement patterns in the pattern element. The keys in a matchdict are strings, while their values are Unicode objects.

在上一个示例中,将 index() 视图函数更改为以下内容 −

In the previous example, change the index() view function to following −

def index(request):
   return Response(str(request.matchdict))

浏览器显示路径参数,其形式为 dict 对象。

The browser displays the path parameters in the form of a dict object.

parameters

当请求与路由模式匹配时,传递给视图函数的请求对象还包括 matched_route 属性。匹配的路由名称可从其名称属性中获取。

When the request matches a route pattern, the request object passed to the view function also includes a matched_route attribute. The name of the matched route can be obtained from its name property.

Example

在以下示例中,我们定义了两个视图函数 student_view() 和 book_view() ,借助于 @view.config() 装饰器。

In the following example, we have two view functions student_view() and book_view() defined with the help of @view.config() decorator.

应用程序的注册表被配置为有两种对应的路由 - 映射到 '/student/{name}/{age}' 模式的 'student' 和映射到 '/book/{title}/{price}' 模式的 'book'。我们调用 configurator 对象的 scan() 方法来添加视图。

The application’s registry is configured to have two corresponding routes – 'student' mapped to '/student/{name}/{age}' pattern and 'book' mapped to '/book/{title}/{price}' pattern. We call the scan() method of configurator object to add the views.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='student')
def student_view(request):
   return Response(str(request.matchdict))
@view_config(route_name='book')
def book_view(request):
   title=request.matchdict['title']
   price=request.matchdict['price']
   return Response('Title: {}, Price: {}'.format(title,price))
if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('student', '/student/{name}/{age}')
      config.add_route('book', '/book/{title}/{price}')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

当浏览器给出 http://localhost:6543/student/Ravi/21 URL 时,输出如下

When the browser is given http://localhost:6543/student/Ravi/21 URL, the output is

{'name': 'Ravi', 'age': '21'}

如果输入的 URL 是 http://localhost:6543/book/Python/300 ,则输出如下

If the URL entered is http://localhost:6543/book/Python/300, the output is

Title: Python, Price: 300

Python Pyramid - View Configuration

术语“视图配置”指的是将视图可调用对象(一个函数、方法或类)与路由配置的信息关联的机制。Kimono 为给定的 URL 模式找到最佳可调用对象。

The term "View Configuration" refers to the mechanism of associating a view callable (a function, method or a class) with the information of route configuration. Pyramid finds the best callable for the given URL pattern.

有三种配置 view 的方式 −

There are three ways to configure a view

  1. Using add_view() method

  2. Using @view_config() decorator

  3. Using @view_defaults () class decorator

Using add_view() Method

这是通过调用 Configurator 对象的 add_view() 方法来严格配置视图的最简单方法。

This is the simplest method of configuring a view imperatively by calling the add_view() method of the Configurator object.

此方法使用以下参数 −

This method uses the following arguments −

  1. name − The view name required to match this view callable. If name is not supplied, the empty string is used (implying the default view).

  2. context − This resource must be an object of a Python class in order for this view to be found and called. If context is not supplied, the value None, which matches any resource, is used.

  3. route_name − This value must match the name of a route configuration declaration that must match before this view will be called. If route_name is supplied, the view callable will be invoked only when the named route has matched.

  4. request_type − an interface that the request must provide in order for this view to be found and called.

  5. request_method − a string (such as "GET", "POST", "PUT", "DELETE", "HEAD", or "OPTIONS") representing an HTTP REQUEST_METHOD or a tuple containing one or more of these strings. The view will be called only when the method attribute of the request matches a supplied value.

  6. request_param − This argument can be any string or a sequence of strings. The view will only be called when the request.params dictionary has a key which matches the supplied value.

Example

在以下示例中,定义了两个函数 getview()postview() ,并将其与两个同名路由相关联。这些函数仅仅返回调用它们的 HTTP 方法的名称。

In the following example, two functions getview() and postview() are defined and associated with two routes of the same name. These functions just return the name of the HTTP method by which they are called.

在使用 GET 方法请求 URL /get 时,将调用 getview() 函数。类似地,在 POST 方法请求 /post 路径 ID 时,将执行 postview() 函数。

The getview() function is called when the URL /get is requested using GET method. Similarly, the postview() function is executed when /post path id requested by POST method.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
def getview(request):
   ret=request.method
   return Response('Method: {}'.format(ret))
def postview(request):
   ret=request.method
   return Response('Method: {}'.format(ret))

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('getview', '/get')
      config.add_route('postview', '/post')
      config.add_view(getview, route_name='getview',request_method='GET')
      config.add_view(postview,route_name='postview', request_method='POST')
      app = config.make_wsgi_app()
      server = make_server('0.0.0.0', 6543, app)
      server.serve_forever()

虽然可以使用 Web 浏览器作为 HTTP 客户端发送 GET 请求,但不能将其用于 POST 请求。因此,我们使用 CURL 命令行实用程序。

While the GET request can be sent by using the web browser as HTTP client, it is not possible to use it for POST request. Hence, we use the CURL command line utility.

C:\Users\Acer>curl localhost:6543/get
Method: GET
C:\Users\Acer>curl -d "param1=value1" -H "Content-Type: application/json" -X POST http://localhost:6543/post
Method: POST

如前所述, request_method 参数可以是一个或多个 HTTP 方法的列表。不妨修改上述程序并定义一个 oneview() 函数来标识导致其执行的 HTTP 方法。

As mentioned earlier, the request_method parameter can be a list of one or more HTTP methods. Let us modify the above program and define a single oneview() function that identifies the HTTP method that causes its execution.

def oneview(request):
   ret=request.method
   return Response('Method: {}'.format(ret))

此函数在应用程序的配置中为所有 HTTP 方法注册。

This function is registered in the application’s configuration for all the HTTP methods.

config.add_route('oneview', '/view')
config.add_view(oneview, route_name='oneview',
   request_method=['GET','POST', 'PUT', 'DELETE'])

Output

CURL 输出如下所示:

The CURL output is shown as below −

C:\Users\Acer>curl localhost:6543/view
Method: GET
C:\Users\Acer>curl -d "param1=value1" -H "Content-Type: application/json" -X POST http://localhost:6543/view
Method: POST
C:\Users\Acer>curl -d "param1=value1" -H "Content-Type: application/json" -X PUT http://localhost:6543/view
Method: PUT
C:\Users\Acer>curl -X DELETE http://localhost:6543/view
Method: DELETE

Using @view_config() Decorator

除了命令式添加视图,还可以使用 @view_config 装饰器将配置的路由与函数、方法甚至可调用的类关联起来。

Instead of adding views imperatively, the @view_config decorator can be used to associate the configured routes with a function, a method or even a callable class.

Example

如申明式配置部分所述,可以将注册的路由与函数关联起来,如下例所示:

As described in the Declarative Configuration section, a registered route can be associated with a function as in the following example −

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
@view_config(route_name='hello')
def hello_world(request):
   return Response('Hello World!')
if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('hello', '/')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

请注意,只有在调用 scan() 方法后,才会将视图添加到应用程序配置中。虽然消除了命令式添加视图的需要,但性能可能会略微下降。

Note that the views are added into the application configuration only after calling the scan() method. While removes the need for imperatively adding the views, the performance may be slightly slower.

Output

view_config() 装饰器还可以传递与 add_view() 方法相同的参数。可以省略所有参数。

The view_config() decorator can also be given same arguments as that of add_view() method. All arguments may be omitted.

@view_config()
def hello_world(request):
   return Response('Hello World!')

在这种情况下,该函数将使用任何路由名称、任何请求方法或参数进行注册。

In such a case, the function will be registered with any route name, any request method or parameters.

view_config 装饰器在可调用视图函数的定义之前放置,如以上示例所示。如果要将类用作视图可调用对象,也可以将它放在类的顶部。此类必须具有 call () 方法。

The view_config decorator is placed just before the definition of callable view function, as in the above example. It can also be put on top of a class if it is to be used as the view callable. Such a class must have a call() method.

在以下 Pyramid 应用程序代码中, MyView 类用作可调用对象,并由 @view_config 装饰器装饰。

In the following Pyramid application code, the MyView class is used as a callable and is decorated by the @view_config decorator.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='hello')
class MyView(object):
   def __init__(self, request):
      self.request = request

   def __call__(self):
      return Response('hello World')

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('hello', '/')
      #config.add_view(MyView, route_name='hello')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

请注意,我们可以通过显式调用 add_view() 方法来添加视图,而不扫描视图配置。

Note that instead of scanning for view configurations, we can add views by explicitly calling the add_view() method.

Example

如果类中的方法必须与不同的路由关联,则应在每个方法的顶部使用不同的 @view_config(),如以下示例中所示。在此,我们有两个方法绑定到两个单独的路由。

If the methods in a class have to be associated with different routes, separate @view_config() should be used on top of each one of them, as done in the following example. Here, we have two methods bound to two separate routes.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import

class MyView(object):
   def __init__(self, request):
      self.request = request

   @view_config(route_name='getview', request_method='GET')
   def getview(self):
      return Response('hello GET')
   @view_config(route_name='postview', request_method='POST')
   def postview(self):
      return Response('hello POST')

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('getview', '/get')
      config.add_route('postview', '/post')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

以下是 CURL 命令的输出:

Here’s the output of CURL commands −

C:\Users\Acer>curl localhost:6543/get
hello GET
C:\Users\Acer>curl -d "param1=value1" -H "Content-Type: application/json" -X POST http://localhost:6543/post
hello POST

Using @view_defaults() Decorator

view_defaults() 是一个类装饰器。如果你必须将类中的方法作为带有某些常见参数和某些特定参数的视图添加,可以在类的顶部 view_defaults() 装饰器中指定常见参数,通过每个方法前面的单独的 view_config() 执行每个方法的配置。

view_defaults() is a class decorator. If you have to add the methods in a class as view with some common parameters and some specific parameters, the common parameters can be specified in the view_defaults() decorator on top of the class, performing configuration of each method by a separate view_config() before each one of them.

Example

在下面的代码中,我们有不同的方法响应相同的路由但具有不同的 request_method 。因此,我们将路由名称定义为默认,并在每个视图配置中指定 request_method

In the following code, we have different methods responding to the same route but with different request_method. Hence we define the rout name as default, and specify the request_method in each view configuration.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.view import view_defaults

@view_defaults(route_name='myview')
class MyView(object):
   def __init__(self, request):
      self.request = request

   @view_config( request_method='GET')
   def getview(self):
      return Response('hello GET')
   @view_config(request_method='POST')
   def postview(self):
      return Response('hello POST')
   @view_config(request_method='PUT')
   def putview(self):
      return Response('hello PUT')
   @view_config(request_method='DELETE')
   def delview(self):
      return Response('hello DELETE')

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('myview', '/view')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

向服务器发出不同 HTTP 请求的 CURL 命令如下所示−

The CURL commands with different HTTP requests to the server are as follows −

C:\Users\Acer>curl localhost:6543/view
hello GET
C:\Users\Acer>curl -d "param1=value1" -H "Content-Type: application/json" -X POST http://localhost:6543/view
hello POST
C:\Users\Acer>curl -d "param1=value1" -H "Content-Type: application/json" -X PUT http://localhost:6543/view
hello PUT
C:\Users\Acer>curl -X DELETE http://localhost:6543/view
hello DELETE

Python Pyramid - Route Prefix

很多时候,相同的 URL 模式会使用多个 Python 代码模块向不同的路由注册。例如,我们有一个 student_routes.py ,其中 /list 和 /add URL 模式分别向“列表”和“添加”路由注册。与这些路由关联的视图函数分别是 list()add()

Many times, similar URL patterns are registered with different routes in more than one Python code modules. For example, we have a student_routes.py where /list and /add URL patterns are registered with 'list' and 'add' routes. The view functions associated with these routes are list() and add(), respectively.

#student_routes.py
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config( route_name='add')
def add(request):
   return Response('add student')
@view_config(route_name='list')
def list(request):
   return Response('Student list')

def students(config):
   config.add_route('list', '/list')
   config.add_route('add', '/add')
   config.scan()

当调用 students() 函数时,最终将注册这些路由。

These routes will eventually be registered when the students() function is called.

与此同时,book_routes.py 中注册了相同的 URL /listadd/ 到“显示”和“新建”路由。它们关联的视图分别是 list() 和 add()。该模块具有添加路由的 books() 函数。

At the same time, there is book_routes.py, in which the same URLs /list and add/ are registered to 'show' and 'new' routes. Their associated views are list() and add() respectively. The module has books() function which adds the routes.

#book_routes.py
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config( route_name='new')
def add(request):
   return Response('add book')
@view_config(route_name='show')
def list(request):
   return Response('Book list')
def books(config):
   config.add_route('show', '/list')
   config.add_route('new', '/add')
   config.scan()

显然,URL 模式之间存在冲突,因为“/list”和“/add”各指向两个路由,并且必须解决此冲突。这是通过使用 config.include() 方法的 route_prefix 参数来完成的。

Obviously, there is a conflict between URL patterns as '/list' and '/add' point to two routes each and this conflict must be resolved. This is done by using the route_prefix parameter of the config.include() method.

config.include() 的第一个参数是添加 route 的函数,第二个参数是 route_prefix 字符串,它将添加到已包含函数中使用的 URL 模式。

The first parameter to config.include() is the function which adds the routes, and the second is the route_prefix string which will be prepended to the URL pattern used in the included function.

因此,语句

Hence, the statement

config.include(students, route_prefix='/student')

会将“/list”URL 模式更改为 '/student/list',而“/add”变为 'student/add'。类似地,我们可以在 books() 函数中为这些 URL 模式添加前缀。

will result in the '/list' URL pattern changed to '/student/list' and '/add' becomes 'student/add'. Similarly, we can add prefix to these URL patterns in the books() function.

config.include(books, route_prefix='/books')

Example

用于启动服务器的代码如下 −

The code that starts the server is as below −

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from student_routes import students
from book_routes import books

if __name__ == '__main__':
   with Configurator() as config:
      config.include(students, route_prefix='/student')
      config.include(books, route_prefix='/book')
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

让我们运行以上代码,并通过执行 CURL 命令来测试这些路由。

Let us run the above code and test the routes by following CURL commands.

C:\Users\Acer>curl localhost:6543/student/list
Student list
C:\Users\Acer>curl localhost:6543/student/add
add student
C:\Users\Acer>curl localhost:6543/book/add
add book
C:\Users\Acer>curl localhost:6543/book/list
Book list

Python Pyramid - Templates

默认情况下,视图函数的响应的内容类型为纯文本。为了呈现 HTML,响应主体文本可能包括 HTML 标签,如下例所示 −

By default, the content-type of the response of a view function is in plain text. In order to render HTML, the text of the response body may include HTML tags, as in the following example −

Example

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
   return Response('<h1 style="text-align:center;">Hello World!</h1>')

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('hello', '/')
      config.add_view(hello_world, route_name='hello')
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

在启动服务器(通过运行以上代码)之后,访问 http://localhost:6543/ ,浏览器渲染以下输出 −

After starting the server (by running the above code), visit to http://localhost:6543/, the browser renders following output −

templates

但是,这种渲染 HTML 的方法,尤其是如果它可能包含某些可变数据,是极其繁琐的。出于此目的,Web 框架使用模板库。模板库将可变数据与其他静态 HTML 代码合并,以动态生成和呈现网页。

However, this method of rendering HTML, especially if it is likely to contain certain variable data, is extremely cumbersome. For this purpose, web frameworks use templating libraries. A template library merges the variable data with the otherwise static HTML code to generate and render web pages dynamically.

Template Bindings

Pyramid 提供了使用诸如 jinja2、Mako 和 Chameleon 等流行模板库的绑定来提供模板支持。

Pyramid provides templating support with the help of bindings to popular template libraries such as jinja2, Mako and Chameleon.

Template Language

Pyramid Bindings

Default Extensions

Chameleon

pyramid_chameleon

.pt, .txt

Jinja2

pyramid_jinja2

.jinja2

Mako

pyramid_mako

.mak, .mako

首先,我们需要为使用所需的模板库安装相应的 Python 库。例如,要使用 jinja2 模板,请使用 PIP 安装程序安装 pyramid_jinja2

First of all, we need to install the corresponding Python library for using the required template library. For example, to use jinja2 template, install pyramid_jinja2 using PIP installer.

pip3 install pyramid_jinja2

然后,我们需要将其包含在应用程序配置中。

Then we need to include it in the application configuration.

config.include('pyramid_jinja2')

pyramid.renderers 模块定义了 render_to_response() 函数。它与以下参数一起使用 −

The pyramid.renderers module defines render_to_response() function. It is used with following parameters −

render_to_response(renderer_name, value, request)

renderer_name 是模板网页,通常保存在应用程序目录的子文件夹 templates 中,value 参数是作为上下文传递给模板的字典,以及从 WSGI 环境中获取的请求对象。

The renderer_name is the template web page, usually saved in the templates subfolder of the application directory, the value parameter is a dictionary passed as a context to the template, and the request object obtained from WSGI environment.

将以下 HTML 脚本另存为 hello.jinja2 目录中的 templates

Save the following HTML script as hello.jinja2 in the templates folder.

<html>
   <body>
      <h1>Hello, {{ name }}!</h1>
   </body>
</html>

Jinja2 Template Library

这里,“name”是 jinja2 模板变量。jinja2 模板语言使用以下语法在 HTML 脚本中插入变量和编程构造:

Here, 'name' is a jinja2 template variable. The jinja2 template language inserts variables and programming constructs in the HTML scripts using following syntax −

Expressions

  1. {{ …​ }} for Expressions to print to the template output.

  2. {% …​ %} for Statements.

  3. {# …​ #} for Comments not included in the template output.

Conditionals

  1. {% if expr %}

  2. {% else %}

  3. {% endif %}

Loop

  1. {% for var in iterable %}

  2. {% endfor %}

在 {{ name }},上下文变量“name”的值在视图响应中动态呈现在 jinja2 中。

In hello.jinja2 {{ name }}, the value of 'name' context variable is dynamically rendered in the view response.

Rendering Template

hello_world() 视图函数通过调用 render_to_response() 函数直接呈现此模板。它还将上下文值发送到模板。

The hello_world() view function directly renders this template by calling render_to_response() function. It also sends a context value to the template.

from pyramid.renderers import render_to_response

def hello_world(request):
   return render_to_response('templates/hello.jinja2',{'name':'Tutorialspoint'},
request=request)

Example

与往常一样,此视图被添加到 hello 路由,指向 / URL。完整的应用程序代码如下 −

As usual, this view is added to the hello route, pointing to / URL. The complete application code is as follows −

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.renderers import render_to_response

def hello_world(request):
   return render_to_response('templates/hello.jinja2', {'name':'Tutorialspoint'}, request=request)

if __name__ == '__main__':
   with Configurator() as config:
      config.add_route('hello', '/')
      config.include('pyramid_jinja2')
      config.add_view(hello_world, route_name='hello')
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

运行服务器并访问 http://localhost:6543/ 。浏览器显示以下结果 −

Run the server and visit http://localhost:6543/. The browser shows following result −

hellotp

每个视图都返回一个响应对象。 render_to_response() 函数是一个快捷函数,实际上返回一个响应对象。这允许上面的 hello_world 视图直接返回其对 render_to_response() 的调用的结果。

Every view must return a response object. The render_to_response() function is a shortcut function that actually returns a response object. This allows the hello_world view above to simply return the result of its call to render_to_response() directly.

另一方面, pyramid.renderers.render() 函数将模板渲染为字符串。我们可以直接生成一个响应对象,并使用该字符串作为响应的主体。

On the other hand, pyramid.renderers.render() function renders a template to a string. We can manufacture a response object directly, and use that string as the body of the response.

让我们按如下方式更改 hello_world() 视图函数 −

Let us change the hello_world() view function as follows −

from pyramid.renderers import render

def hello_world(request):
   retval = render('templates/hello.jinja2',
   {'name':'Tutorialspoint'}, request=request)
   return Response(retval)

其余代码保持不变,浏览器也显示与上面相同的输出。

Remaining code being same, the browser also shows the same output as above.

Rendering via Configuration

正如前面提到的,Pyramid 的视图可调用 id 返回的 HTTP 响应的 content_type 是 text/plain。但是,如果 @view_config 装饰器的 renderer 参数被分配了其中任何一个值,它可以被更改为字符串、JSON 或 JSONP。因此,Pyramid 具有以下内置渲染器 −

As mentioned earlier, the content_type of HTTP response returned by Pyramid’s view callable id text/plain. However, it can be altered to string, JSON or JSONP if the renderer parameter of the @view_config decorator is assigned with any of these values. Pyramid thus have following built-in renderers −

  1. JSON

  2. String

  3. JSONP

Example

在以下示例中,hello_world() 视图函数被配置为渲染 JSON 响应。

In the following example, the hello_world() view function is configured to render JSON response.

from pyramid.view import view_config

@view_config(route_name='hello',renderer='json')
def hello_world(request):
   return {'content':'Hello World!'}

Output

将 renderer 类型设置为 JSON 也将 HTTP 响应的 content_type 标头设置为 application/json 。浏览器显示 JSON 响应,如下图所示 −

The setting of renderer type to JSON also sets the content_type header of the HTTP response to application/json. The browser displays the JSON response as in the following figure −

json

@view_config() 装饰器的 renderer 参数可以设置为模板网页(它必须存在于 templates 文件夹中)。先决条件是模板库的适当 Python 绑定必须已安装,并且应用程序配置必须包括绑定。

The renderer parameter of the @view_config() decorator can be set to a template web page (which must be present in the templates folder). The prerequisite conditions are that the appropriate Python binding of the template library must be installed, and the application configuration must include the binding.

我们已经安装了 python_jinja2 包,这样我们就可以使用 jinja2 模板由 hello_world() 视图函数渲染,该函数由带有 renderer 参数的 @view_config() 装饰。

We have already installed python_jinja2 package, so that we can use jinja2 template to be rendered by the hello_world() view function, decorated by @view_config() with renderer parameter.

hello.jinja2 模板 HTML 代码如下 −

The hello.jinja2 template HTML code is as follows −

<html>
   <body>
      <h1>Hello, {{ name }}!</h1>
   </body>
</html>

经过装饰的 hello_world() 函数被写为 −

The decorated hello_world() function is written as −

from pyramid.view import view_config

@view_config(route_name='hello', renderer='templates/hello.jinja2')
def hello_world(request):
   return {'name':'Pyramid!'}

Example

在这种情况下,视图函数返回一个字典对象。它可作为上下文数据提供给模板,它可以在 HTML 文本中借助模板语言语法元素插入。

In this case, the view function returns a dictionary object. It is made available to the template as the context data, that can be inserted in the HTML text with the help of template language syntax elements.

渲染 jinja2 模板的完整代码如下 −

The complete code to render a jinja2 template is as follows −

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='hello', renderer='templates/hello.jinja2')
def hello_world(request):
   return {'name':'Pyramid!'}

if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_route('hello', '/')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

view 函数提供可变数据的模板网页如下所示 −

The template webpage with variable data supplied by the view function looks as below −

view

Add/Change Renderer

模板只不过是散布着模板语言语法的网页。尽管 Pyramid 使用 ".jinja2" 作为 jinja2 模板的默认扩展名,但惯例是使用 ".html" 扩展名作为网页。

Templates are nothing but web pages interspersed with template language syntax. Even though Pyramid uses the default extension of a jinja2 template as ".jinja2", the established practice is to use the ".html" extension of web pages.

我们可以更改应用程序配置以添加使用 ".html" 扩展名。这是通过 add_jinja2_renderer 完成的。

We can change the application configuration to let the .html extension be used in addition to ".jinja2". This is done by the add_jinja2_renderer.

config.add_jinja2_renderer(".html")

hello.jinja2 模板现在被重命名为 hello.html。为了能够使用此模板,让我们将视图函数的定义更改为以下代码 −

The hello.jinja2 template is now renamed as hello.html. To be able to use this template, let us change the view function definition to the following code −

from pyramid.view import view_config

@view_config(route_name='hello', renderer='templates/hello.html')
def hello_world(request):
   return {'name':'Pyramid!'}

同时,通过添加 ".html" 渲染器来修改 Configurator 对象的属性。

Simultaneously, we modify the Configurator object’s properties by adding the ".html" renderer.

if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route(hello, '/')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Template Context from matchdict

如前所述,如果路由配置中的 URL 模式包含一个或多个占位符参数,它们从请求 URL 中的值将与请求一起作为 matchdict 对象传递,而该对象反过来可以作为上下文数据传递给要渲染的模板。

As explained earlier, if the URL pattern in the route configuration consists of one or more placeholder parameters, their values from the request URL are passed along with the request as a matchdict object, which in turn can be passed as context data to the template to be rendered.

对于我们的下一个示例, hello.html - Jinja2 模板保持不变。

For our next example, the hello.html - the jinja2 template remains the same.

<html>
   <body>
      <h1>Hello, {{ name }}!</h1>
   </body>
</html>

我们知道上下文变量 'name' 的值由视图函数传递。但是,我们并不是传递一个硬编码值(如前一个示例中所示),而是从 matchict 对象中获取它的值。此对象是由 URL 字符串中的路径参数填充的。

We know that the value for the context variable 'name' is passed by the view function. However, instead of passing a hardcoded value (as in the previous example), its value is fetched from the matchict object. This object is populated by the path parameters in the URL string.

from pyramid.view import view_config

@view_config(route_name='index', renderer='templates/hello.html')
def index(request):
   return {'name':request.matchdict['name']}

Example

修改后的应用程序代码如下所示 −

The modified application code is given below −

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='index', renderer='templates/hello.html')
def index(request):
   return {'name':request.matchdict['name']}
if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('index', '/{name}')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

启动服务器,打开浏览器,输入 URL http://localhost:6543/Tutorialspoint 。结尾字符串成为 matchdict 中 'name' 键的值。它被 Jinja2 模板使用,并渲染出以下输出。

Start the server, open the browser and enter the URL http://localhost:6543/Tutorialspoint. The tailing string becomes the value of 'name' key in the matchdict. It is utilized by the jinja2 template and following output is rendered.

jinja2

Conditionals and Loops in Template

Jinja2 模板语言允许在 HTML 脚本中包含条件语句和循环构造。Jinja2 用于这些编程元素的语法如下所示 −

The jinja2 template language allows conditional statements and looping constructs to be included in the HTML script. The jinja2 syntax for these programming elements is as follows −

Conditionals

{% if expr %}
HTML
{% else %}
HTML
{% endif %}

Loop

{% for var in iterable %}
HTML
{% endfor %}

不难看出,Jinja2 语法与 Python 的 if 和 for 语句非常相似。不过,Jinja2 不使用缩进来标记块。相反,对于每个 if 都必须有一个 endif 语句。类似地,对于每个 for 语句,都必须有一个 endfor 语句。

It can be seen that the jinja2 syntax is very much similar to Python’s if and for statements. Except that, jinja2 doesn’t use the indentations to mark the blocks. Instead, for each if there has to be an endif statement. Similarly, for each for statement, there has to be a endfor statement.

Example

以下示例演示了模板条件和循环语句的使用。首先,Pyramid 代码使用一个 students 作为词典对象的列表,每个词典包含一个学生的 ID、姓名和百分比。此列表对象作为上下文传递给 marklist.html 模板

Following example demonstrates the use of template conditional and loop statements. First, the Pyramid code uses a students as a list of dictionary objects, each dictionary having id, name and percentage of a student. This list object is passed as a context to the marklist.html template

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

students = [
   {"id": 1, "name": "Ravi", "percent": 75},
   {"id": 2, "name": "Mona", "percent": 80},
   {"id": 3, "name": "Mathews", "percent": 45},
]

@view_config(route_name='index', renderer='templates/marklist.html')

def index(request):
   return {'students':students}
if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('index', '/')
      config.scan()
   app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

将此程序保存为 marklist.py。现在,必须将以下 HTML 脚本保存为 marklist.html。它遍历从视图函数接收的 students 列表对象,并将学生数据渲染为一个 HTML 表格。第四列使用 Jinja2 if 语句语法显示通过/失败结果。

Save this program as marklist.py. Now, the following HTML script has to be save as marklist.html. It traverses the students list object received from the view function, and renders the student data in the form of a HTML table. The fourth column shows pass/fail result, using the jinja2 if statement syntax.

<html>
<body>
   <table border=1>
      <thead>
         <tr>
            <th>Student ID</th> <th>Student Name</th>
            <th>percentage</th>
            <th>Result</th>
         </tr>
      </thead>
      <tbody>
         {% for Student in students %}
            <tr>
               <td>{{ Student.id }}</td>
               <td>{{ Student.name }</td>
               <td>{{ Student.percent }}</td>
               <td>
                  {% if Student.percent>=50 %}
                  Pass
                  {% else %}
                  Fail
                  {% endif %}
               </td>
            </tr>
         {% endfor %}
      </tbody>
   </table>
</body>
</html>

Output

运行 marklist.py 代码。 http://localhost:6543/ 链接渲染出以下表格结果 −

Run the marklist.py code. The http://localhost:6543/ link renders the following tabular result −

marklist

Python Pyramid - HTML Form Template

在本章中,我们将看到 Pyramid 如何从 HTML 表单读取数据。让我们将以下 HTML 脚本保存为 myform.html 。我们将使用它来获取 Template 对象并对其进行渲染。

In this chapter, we shall see how Pyramid 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:6543/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" value="Submit"> </p>
</body>
</html>

Pyramid 对象的配置中添加了一个 "index" 路由,映射到以下 index() 函数,该函数渲染上述 HTML 表单 −

An "index" route added in Pyramid object’s configuration is mapped to the following index() function, which renders the above HTML form −

@view_config(route_name='index', renderer='templates/myform.html')
def index(request):
   return {}

正如我们所看到的,用户输入的数据通过 POST 请求传递到 /students URL。因此,我们将添加一个 'students' 路由来匹配 /students 模式,并将其与 add() 视图函数关联,如下所示 −

As we can see, the data entered by user is passed to /students URL by POST request. So, we shall add a 'students' route to match the /students pattern, and associate it with add() view function as follows −

@view_config(route_name='students', renderer='templates/marklist.html')
def add(request):
   student={'id':request.params['id'],
      'name':request.params['name'],
      'percent':int(request.params['percent'])} 9. Pyramid – HTML Form Template
   students.append(student)
   return {'students':students}

POST 请求发送的数据以 request.params 对象的形式在 HTTP 请求对象中可用。它是 HTML 表单属性及其由用户输入的值的字典。解析此数据并将其追加到词典对象的 students 列表中。更新后的 students 对象作为上下文数据传递给 marklist.html 模板。

The data sent by POST request is available in the HTTP request object in the form of request.params object. It is a dictionary of HTML form attributes and their values as entered by the user. This data is parsed and appended to students list of dictionary objects. The updated students object is passed to the marklist.html template as a context data.

marklist.html web 模板与前一个示例中使用的相同。它显示一个带有计算结果列的学生数据表。

The marklist.html web template as the same as used in the previous example. It displays a table of student data along with the computed result column.

<html>
<body>
   <table border=1>
      <thead>
         <tr>
            <th>Student ID</th> <th>Student Name</th>
            <th>percentage</th>
            <th>Result</th>
         </tr>
      </thead>
      <tbody>
         {% for Student in students %}
            <tr>
               <td>{{ Student.id }}</td>
               <td>{{ Student.name }}</td>
               <td>{{ Student.percent }}</td>
               <td>
                  {% if Student.percent>=50 %}
                  Pass
                  {% else %}
                  Fail
                  {% endif %}
               </td>
            </tr>
         {% endfor %}
      </tbody>
   </table>
</body>
</html>

Example

下面给出了用于渲染 HTML 表单、解析表单数据和生成显示学生成绩表表格的页面的完整代码,其中包含视图 −

The complete code containing views for rendering the HTML form, parsing the form data and generating a page showing the students marklist table is given below −

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

students = [
   {"id": 1, "name": "Ravi", "percent": 75},
   {"id": 2, "name": "Mona", "percent": 80},
   {"id": 3, "name": "Mathews", "percent": 45},
]

@view_config(route_name='index', renderer='templates/myform.html')
def index(request):
   return {}
@view_config(route_name='students', renderer='templates/marklist.html')
def add(request):
   student={'id':request.params['id'], 'name':request.params['name'],
'percent':int(request.params['percent'])}
   students.append(student)
   return {'students':students}

if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('index', '/')
      config.add_route('students','/students')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

要启动服务器,请从命令行运行上述 Python 代码。在浏览器中,访问 http://localhost:6543/ 以获取如下所示的表单 −

To start the server, run the above Python code from command line. In your browser, visit http://localhost:6543/ to get the form as shown below −

student

输入示例数据,所示,然后按提交按钮。浏览器将被定向到 /students URL,它反过来调用 add() 视图。结果是显示新输入的学生新数据的成绩单表。

Enter a sample data as shown and press submit button. The browser is directed to /students URL, which in turn invokes the add() view. The result is a table of marklist showing the newly entered data of a new student.

students url

Python Pyramid - Static Assets

通常需要在模板响应中包含一些即使存在某些动态数据也不会更改的资源。此类资源称为静态资产。媒体文件(.png、.jpg 等)、用于执行某些前端代码的 JavaScript 文件或用于格式化 HTML(.css 文件)的样式表是静态文件示例。

Often it is required to include in the template response some resources that remain unchanged even if there is a certain dynamic data. Such resources are called static assets. Media files (.png, .jpg etc), JavaScript files to be used for executing some front end code, or stylesheets for formatting HTML (.css files) are the examples of static files.

Pyramid 从服务器文件系统中的指定目录向客户端浏览器提供这些静态资产。Configurator 对象的 add_static_view() 方法定义了包含图像、JavaScript 和 CSS 文件等静态文件的文件夹的路由和路径名称。

Pyramid serves these static assets from a designated directory in the server’s filesystem to the client’s browser. The add_static_view() method of the Configurator object defines the name of the route and path for the folder containing the static files such as images, JavaScript, and CSS files.

根据约定,“static”目录用于存储静态资产,而 add_static_view() 的使用方法如下:

As a convention, the 'static' directory is used to store the static assets and the add_static_view() is used as follows −

config.add_static_view(name='static', path='static')

定义静态路由后,可以使用 request.static_url() 方法在 HTML 脚本中使用静态资产的路径。

Once the static route is defined, the path of static assets while using in HTML script can be obtained by request.static_url() method.

Static Image

在以下示例中,Pyramid 徽标将在 logo.html 模板中呈现。因此,首先将 "pyramid.png" 文件放在 static 文件夹中。现在可以将它用作 HTML 代码中 <img> 标记的 src 属性。

In the following example, Pyramid logo is to be rendered in the logo.html template. Hence, "pyramid.png" file is first placed in static folder. It is now available for using as src attribute of <img> tag in HTML code.

<html>
<body>
   <h1>Hello, {{ name }}. Welcome to Pyramid</h1>
   <img src="{{request.static_url('app:static/pyramid.png')}}">
</body>
</html>

Example

应用程序代码使用 add_static_view() 更新配置器并定义 index() 视图呈现上述模板。

The application code updates the configurator with add_static_view(), and defines index() view renders the above template.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='index', renderer='templates/logo.html')

def index(request):
   return {'name':request.matchdict['name']}

if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('index', '/{name}')
      config.add_static_view(name='static', path='app:static')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

Output

运行上述代码以启动服务器。在浏览器中使用 http://localhost:6543/Guest 作为 URL。在此,“Guest”是 matchdict 对象中的视图函数选取的路径参数,并作为上下文传递给 logo.html 模板。现在浏览器显示 Pyramid 徽标。

Run the above code to start the server. Use http://localhost:6543/Guest as the URL in your browser. Here 'Guest' is the path parameter picked up by the view function in matchdict object and passed to the logo.html template as the context. The browser displays the Pyramid logo now.

pyramid

Javascript as Static Asset

以下是静态文件的另一个示例。JavaScript 代码 hello.js 包含 myfunction() 定义,将在 HTML 脚本 (templates\hello.html) 中的 onload 事件中执行

Here is another example of static file. A JavaScript code hello.js contains a definition of myfunction() to be executed on the onload event in following HTML script (templates\hello.html)

<html>
<head>
   <script src="{{request.static_url('app:static/hello.js')}}"></script>
</head>
<body onload="myFunction()">
   <div id="time" style="text-align:right; width="100%"></div>
   <h1><div id="ttl">{{ name }}</div></h1>
</body>
</html>

Example

存储在 static 文件夹中的 hello.js 代码如下:

The hello.js code saved in static folder is as follows −

function myFunction() {
   var today = new Date();
   var h = today.getHours();
   var m = today.getMinutes();
   var s = today.getSeconds();
   var msg="";
   if (h<12)
   {
      msg="Good Morning, ";
   }
   if (h>=12 && h<18)
   {
      msg="Good Afternoon, ";
   }
   if (h>=18)
   {
      msg="Good Evening, ";
   }
   var x=document.getElementById('ttl').innerHTML;
   document.getElementById('ttl').innerHTML = msg+x;
   document.getElementById('time').innerHTML = h + ":" + m + ":" + s;
}

Output

该函数检测当前时间的值,并根据一天中的时间为 msg 变量分配适当的值(早上好、下午好或晚上好)。

The function detects the value of current time and assigns appropriate value to msg variable (good morning, good afternoon or good evening) depending on the time of the day.

hello.js 存储在 static 文件夹中,将 hello.html 存储在 templates 文件夹中并重新启动服务器。浏览器应显示当前时间及其下方相应的提示信息。

Save hello.js in static folder, hello.html in templates folder and restart the server. The browser should show the current time and corresponding message below it.

goodevening

Python Pyramid - Request Object

视图可调用函数的功能涉及从 WSGI 环境获取请求数据,并在处理后向客户端返回某个 HTTP 响应。视图函数将 Request 对象作为参数接收。

The functionality of a view callable involves obtaining the request data from the WSGI environment and returning a certain HTTP response back to the client after processing. The view function receives the Request object as the argument.

通常此对象不会由用户实例化。相反,它封装了 WSGI 环境字典。此 request 对象表示“pyramid.request.Request 类”。它具有许多属性和方法,视图函数使用它们来处理请求数据。

Normally this object is not instantiated by the user. Instead, it encapsulates the WSGI environ dictionary. This request object represents "pyramid.request.Request class." It possesses a number of attributes and methods, using which the request data is processed by the view function.

以下是一些 attributes

Here are some of the attributes

  1. request.method − The HTTP request method used by the client to send the data, e.g., GET, POST

  2. request.GET − This attribute is a multidict with all the variables in the query string.

  3. request.POST − This attribute is available only if the request was a POST and it is a form submission. It is a multidict with all the variables in the request body.

  4. request.params − A combined multidict of everything in request.GET and request.POST.

  5. request.body − This attribute contains the entire request body as a string. This is useful when the request is a POST that is not a form submission, or a request like a PUT.

  6. request.cookies − Contains all the cookies.

  7. request.headers − A case-insensitive dictionary of all the HTTP headers.

除了上述特定 HTTP 环境属性外,Pyramid 还会添加某些特殊属性。

In addition to the above HTTP specific environment attributes, Pyramid also adds certain special attributes.

  1. request.url − Returns the full request URL with query string, e.g., [role="bare"]http://localhost:6543/app?name=Ravi

  2. request.host − The host information in the URL, e.g., localhost

  3. request.host_url − This attribute returns the URL with the host, e.g., [role="bare"]http://localhost:6543/

  4. request.application_url − The URL of the application (without PATH_INFO), e.g., [role="bare"]http://localhost:6543/app

  5. request.path_url − Contains the URL of the application including the PATH_INFO, e.g., [role="bare"]http://localhost:66543/app

  6. request.path − Returns The URL including PATH_INFO without the host, e.g., "/app"

  7. request.path_qs − the query string in the URL including PATH_INFO, e.g., "/app?name=Ravi"

  8. request.query_string − Only the query string in the URL, e.g., "name=Ravi"

Python Pyramid - Response Object

Response 类定义在 pyramid.response 模块中。此类的对象由视图可调用项返回。

The Response class is defined in pyramid.response module. An object of this class is returned by the view callable.

from pyramid.response import Response
def hell(request):
   return Response("Hello World")

响应对象包含一个状态代码(默认为 200 OK),一个响应标头列表和响应正文。大多数 HTTP 响应标头可用作属性。Response 对象可用的属性如下 −

The response object contains a status code (default is 200 OK), a list of response headers and the response body. Most HTTP response headers are available as properties. Following attributes are available for the Response object −

  1. response.content_type − The content type is a string such as – response.content_type = 'text/html'.

  2. response.charset − It also informs encoding in response.text.

  3. response.set_cookie − This attribute is used to set a cookie. The arguments needed to be given are name, value, and max_age.

  4. response.delete_cookie − Delete a cookie from the client. Effectively it sets max_age to 0 and the cookie value to ''.

pyramid.httpexceptions 模块定义了处理错误响应(例如 404 Not Found)的类。这些类实际上是 Response 类的子类。其中一个这样的类是 "pyramid.httpexceptions.HTTPNotFound"。它的典型用法如下 −

The pyramid.httpexceptions module defines classes to handle error responses such as 404 Not Found. These classes are in fact subclasses of the Response class. One such class is "pyramid.httpexceptions.HTTPNotFound". Its typical use is as follows −

from pyramid.httpexceptions import HTTPNotFound
from pyramid.config import view_config
@view_config(route='Hello')
def hello(request):
   response = HTTPNotFound("There is no such route defined")
   return response

我们可以使用 Response 类的 location 属性将客户端重定向到另一个路由。例如 −

We can use location property of Response class to redirect the client to another route. For example −

view_config(route_name='add', request_method='POST')
def add(request):
   #add a new object
   return HTTPFound(location='http://localhost:6543/')

Python Pyramid - Sessions

会话是客户端登录到服务器和注销服务器之间的时间间隔。会话对象也是一个包含会话变量和相关值的键值对的字典对象。在 Pyramid 中,它可用作请求对象的属性。

A session is a time interval between client logs into a server and it logs out of it. Session object is also a dictionary object containing key-value pairs of session variables and associated values. In Pyramid, it is available as an attribute of request object.

为了处理会话机制,必须使用返回会话对象的会话工厂来配置 Pyramid 应用程序对象。Pyramid 内核提供了一个基本会话工厂,它使用 cookie 来存储会话信息。

In order to handle session mechanism, the Pyramid Application object must be configured with a session factory that returns the session object. Pyramid core provides a basic session factory, which uses cookies to store session information.

Default Session Factory

@ {s0} 模块定义了 @ {s1} 类。它的对象需要一个私钥,用于对会话 Cookie 信息进行数字签名。

The pyramid.session module defines SignedCookieSessionFactory class. Its object needs a secret key for digitally signing the session cookie information.

from pyramid.session import SignedCookieSessionFactory
my_session_factory = SignedCookieSessionFactory('abcQWE123!@#')

Configurator 类的 @ {s2} 方法使用此工厂对象来设置会话。

The set_session_factory() method of the Configurator class uses this factory object to set up the session.

config.set_session_factory(my_session_factory)

完成后,会话对象现可用作 @ {s3} 属性进行实现。要添加会话变量,请使用 -

Once this is done, the session object is now available for implementation as request.session attribute. To add a session variable, use −

request.session['user'] = 'Admin'

要检索会话变量,请使用 -

To retrieve a session variable, use −

user=request.session['user']

要移除会话变量,请使用 @ {s4} 方法。

To remove a session variable, use the pop() method.

request.session.pop('user')

Session Example

下面介绍了在 Pyramid 应用程序中使用会话变量。首先,登录路由(与 login() 视图函数相关联)在浏览器上显示登录表单。

Described below is the usage of session variable in a Pyramid application. First, the login route (associated with login() view function) brings up a login form on the browser.

@view_config(route_name='login')
def login(request):
   html="""
   <html>
   <body>
      <form action='/add'> Enter User name :
         <input type='text' name='user'>
         <input type='submit' value='submit'>
      </form>
   </body>
   </html>
   """
return Response(html)

add() 函数读取“用户”表单属性并使用其值添加会话变量。

The add() function reads the 'user' form attribute and uses its value to add a session variable.

@view_config(route_name='addsession')
def add(request):
   request.session['user']=request.params['user']
   return Response("<h2>Session object added.</h2><br><h3><a href='/read'>click here</a></h3>")

read() 视图读回会话变量数据并显示欢迎消息。

The read() view reads back the session variable data and displays a welcome message.

@view_config(route_name='readsession')
def read(request):
   user=request.session['user']
   response="<h2>Welcome {} </h2>".format(user)+"<br><h3><a href='/logout'>Logout</a></h3>"
   return Response(response)

这些视图和会话工厂添加到应用程序配置中。

These views along with the session factory are added in the application configuration.

config.set_session_factory(my_session_factory)
config.add_route('login','/')
config.add_route('logout','/logout')
config.add_route('addsession', '/add')
config.add_route('readsession', '/read')
config.scan('session')

Example

以下是完整代码 −

The complete code is given below −

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.session import SignedCookieSessionFactory
my_session_factory = SignedCookieSessionFactory('abcQWE123!@#')

@view_config(route_name='login')
def login(request):
   html="""
   <html>
   <body>
   <form action='/add'>
      Enter User name :
      <input type='text' name='user'>
      <input type='submit' value='submit'>
   </form>
   </body>
   </html>
"""
   return Response(html)
@view_config(route_name='addsession')
def add(request):
   request.session['user']=request.params['user']
   return Response("<h2>Session object added.</h2><br><h3><a href='/read'>click here</a></h3>")

@view_config(route_name='readsession')
def read(request):
   user=request.session['user']
   response="<h2>Welcome {} </h2>".format(user)+"<br><h3><a href='/logout'>Logout</a>>/<h3>"
   return Response(response)

@view_config(route_name='logout')
def logout(request):
   request.session.pop('user')
   response="<h2>You have been logged out </h2><br><h3><a href='/'>Login</a></h3>"
   return Response(response)

if __name__ == '__main__':
   with Configurator() as config:
      config.set_session_factory(my_session_factory)
      config.add_route('login','/')
      config.add_route('logout','/logout')
      config.add_route('addsession', '/add')
      config.add_route('readsession', '/read')
      config.scan('session')
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

在 Pyramid 虚拟环境文件夹中的子文件夹(称为“会话”)中将此脚本另存为 main.py。请注意,此子文件夹必须有一个空 @ {s5} 文件,才能将其视为包。

Save this script as main.py in a subfolder (called 'session') within the Pyramid virtual environment folder. Note that this subfolder must have an empty init.py file for it to be treated as a package.

Output

运行 main.py 并输入 @ {s6} 在浏览器中打开登录表单。

Run main.py and enter http://localhost:6543/ to open up the login form in the browser.

submit

输入用户名并按“提交”按钮。给定的名称将保存为“用户”会话变量。

Enter the user name and press the "submit" button. The given name is saved as a 'user' session variable.

session

“单击此处”链接读回 @ {s7} 变量并显示欢迎消息。

The 'click here' link reads back the session variable and displays welcome message.

welcome admin

注销链接弹出 @ {s8} 变量并将浏览器带回登录页面。

The logout link pops the session variable and takes the browser back to the login page.

Python Pyramid - Events

Pyramid 应用程序在它的生命周期中发出多种事件。虽然这些事件通常不需要被使用完,通过适当处理这些事件可以执行稍微高级的操作。

A Pyramid application emits various events during the course of its lifetime. Although these events need not be used up normally, slightly advanced operations can be performed by properly handling these events.

Pyramid 框架广播的事件仅在您用某个订阅者函数注册这个事件时才开始有用。必须将发出的事件用作 subscriber 函数的参数。

An event broadcast by the Pyramid framework becomes usable only when you register it with a subscriber function. The emitted event must be used as the argument of the subscriber function.

def mysubscriber(event):
   print("new request")

然而,只有将订阅者函数添加到应用程序配置中后,它才会开始运作,添加方法如下所示 −

However, a subscriber function becomes operational only when it is added to the application’s configuration with the help of add_subscriber() method as shown below −

在下面的代码段中,应用程序配置为订阅者函数在发出 NewRequest 对象时由该函数调用。

In the following snippet, the application is configured so that the subscriber function is invoked when it emits NewRequest object.

from pyramid.events import NewRequest
config.add_subscriber(mysubscriber, NewRequest)

还有一个 @subscriber() 装饰器用于配置事件。

There is also a @subscriber() decorator for configuring the event.

from pyramid.events import NewRequest
from pyramid.events import subscriber

@subscriber(NewRequest)
def mysubscriber(event):
   print ("new request")

与装饰视图配置一样,在这里也必须执行 config.scan() 才能使装饰器生效。

As with the decortive view configuration, here also the config.scan() must be performed for the decorator to be effective.

正如前面提到的,Pyramid 应用程序发出多种类型的事件。这些事件类可用于 pyramid.event 模块内。它们如下所示 −

As mentioned earlier, the Pyramid application emits a variety of event types. These event classes are available in pyramid.event module. They are listed below −

  1. ApplicationCreated − This event is transmitted just when the config.make_wsgi_app() method of the Configurator class is called to return the WSGI application object.

  2. NewRequest − An object of this event class is emitted every time the Pyramid application starts processing an incoming request. This object has a request attribute which is the request object as supplied by WSGI environ dictionary.

  3. ContextFound − The application’s router traverses all the routes and finds an appropriate match with the URL pattern. This is when the object of ContextFound class is instantiated.

  4. BeforeTraversal − An instance of this class is emitted as an event after the Pyramid router has attempted to find a route object but before any traversal or view code is executed.

  5. NewResponse − As the name suggests, this event is raised whenever any Pyramid view callable returns a response. This object has request and response attributes.

  6. BeforeRender − An object of this type is transmitted as an event just before a renderer is invoked. The subscriber function to this event has access to the application’s global data (which is in the form of a dict object) and can modify value of one or more keys.

Python Pyramid - Message Flashing

消息闪现机制由 Web 应用程序框架用于向用户提供有关其与应用程序交互的特定反馈。闪现消息由会话对象保存在队列中。

The mechanism of message flashing is used by web application frameworks to provide certain feedback to the user about his interaction with the application. The flashed messages are held in a queue by the session object.

闪现消息机制允许在某个视图中创建消息并在称作下一视图的视图函数中呈现消息。与前一节一样,我们必须首先启用会话工厂才能处理会话。要向消息队列中添加消息,请使用会话对象的 flash() 方法。

Flash messaging mechanism makes it possible to create a message in one view and render it in a view function called next. As in the previous section, we must enable the session factory first to be able to handle the session. To add a message in the message queue, use flash() method of the session object.

request.session.flash('Hello World')

该会话具有 pop_flash()peek_flash() 方法。pop_flash() 方法从队列中移除最近添加的消息。peek_flash() 方法在队列中有消息时返回 true,在队列为空时返回 false。

The session has pop_flash() and peek_flash() methods. The pop_flash() method removes the last added message from the queue. The peek_flash() method returns true if the queue has a message, false if it is empty.

这两个方法都在一个模板 Web 页面中使用,用于从队列中提取一条或多条消息并将其作为响应的一部分进行呈现。

Both these methods are used in a template web page to fetch one or messages from the queue and render it as a part of the response.

Message Flashing Example

以下示例演示了消息闪现机制。此处的 login() 视图代码检查是否已通过 POST 或 GET 方法调用它。如果该方法是 GET,它将呈现带有用户名和密码字段的登录表单。提交的表单将通过 POST 方法提交到同一个 URL。

The mechanism of message flashing is demonstrated by the example below. Here, the login() view code checks if it has been invoked by POST or GET method. If the method is GET, it renders the login form with username and password fields. The submitted form is submitted with POST method to the same URL.

当检测到 POST 方法时,视图进一步检查输入的有效性并在会话队列中闪现适当的消息。这些错误闪现消息由登录模板本身提取,而在成功闪现消息被闪现后,客户端被重定向到 index() 视图,以呈现 index 模板。

When the POST method is detected, the view further checks the validity of the inputs and flashes appropriate messages to the session queue. These error flash messages are extracted by the login template itself, whereas after the success flash message is flashed, the client is redirected to the index() view to render the index template.

应用程序代码中的两个视图是 −

The two views in the application code are −

@view_config(route_name='login', renderer='templates/login.html')
def login(request):
   if request.method == 'POST':
   if request.POST['password']=='' or request.POST['username']=='':
      request.session.flash('User name and password is required')
      return HTTPFound(location=request.route_url('login'))
   if len(request.POST['password'])in range(1,9):
      request.session.flash('Weak password!')
   if request.POST['username']not in ['admin', 'manager', 'supervisor']:
      request.session.flash('successfully logged in!')
      return HTTPFound(location=request.route_url('index'))
   else:
      request.session.flash('Reserved user ID Forbidden!')
      return HTTPFound(location=request.route_url('login'))
   return {}

@view_config(route_name='index', renderer='templates/index.html')
def index(request):
   return {}

login.html 模板具有以下代码 −

The login.html template has the following code −

<!doctype html>
<html>
<head>
   <style>
      p {background-color:grey; font-size: 150%}
   </style>
</head>
<body>
   <h1>Pyramid Message Flashing Example</h1>
   {% if request.session.peek_flash()%}
      <div id="flash">
         {% for message in request.session.pop_flash() %}
         <p>{{ message }}</p>
         {% endfor %}
      </div>
   {% endif %}
   <h3>Login Form</h3>
   <form action="" method="POST">
      <dl>
         <dt>Username:
            <dd><input type="text" name="username">
         <dt>Password:
         <dd><input type="password" name="password">
      </dl>
      <input type="submit" value="Login">
   </form>
</body>
</html>

在显示登录表单之前,jinja2 模板代码遍历消息队列,在 <div id='flash'> 部分中弹出每条消息。

Before the login form is displayed, the jinja2 template code traverses through the message queue, pops each message in the <div id='flash'> section.

以下是在 login() 视图中插入的成功消息 index.html 的脚本 −

Following is the script for index.html that flashes the success messages inserted by the login() view −

<!doctype html>
<html>
<head>
   <style>
      p {background-color:grey; font-size: 150%}
   </style>
</head>
<body>
   {% if request.session.peek_flash()%}
   <div id="flash">
   {% for message in request.session.pop_flash() %}
   <p>{{ message }}</p>
   {% endfor %}
   {% endif %}
   <h1>Pyramid Message Flashing Example</h1>
   <h3>Do you want to <a href = "/login">
   <b>log in?</b></a></h3>
</body>
</html>

Example

此示例的应用程序代码是: main.py

The application code for this example is main.py

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
from pyramid.session import SignedCookieSessionFactory
from pyramid.httpexceptions import HTTPFound

my_session_factory = SignedCookieSessionFactory(' abcQWE123!@#')
@view_config(route_name='login', renderer='templates/login.html')
def login(request):
   if request.method == 'POST':
      if request.POST['password']=='' or  request.POST['username']=='':
      request.session.flash('User name and password is required')
      return HTTPFound(location=request.route_url('login'))
   if len(request.POST['password'])in range(1,9):
      request.session.flash('Weak password!')
   if request.POST['username']not in ['admin', 'manager', 'supervisor']:
      request.session.flash('successfully logged in!')
      return HTTPFound(location=request.route_url('index'))
   else:
      request.session.flash('Reserved user ID Forbidden!')
      return HTTPFound(location=request.route_url('login'))
   return {}

@view_config(route_name='index', renderer='templates/index.html')
def index(request):
   return {}

if __name__ == '__main__':
   with Configurator() as config:
      config.set_session_factory(my_session_factory)
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('login','/login')
      config.add_route('index','/')
      config.scan('flash')
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

将此程序代码另存为 app.py 在 Pyramid 虚拟环境中的 Flash 子文件夹中,并在其中放置一个 init.py 。将两个模板(“index.html”和“login.html”)存储在 flush\templates 文件夹中。

Save this program code as app.py in a flash subfolder in the virtual environment for Pyramid, putting a blank init.py in it. Store the two templates ("index.html" and "login.html") in flush\templates folder.

Output

运行 main.py,然后单击 http://localhost:6543/login 链接通过浏览器打开登录表单。

Run the main.py and open the login form in the browser by clicking the http://localhost:6543/login link.

pyramid message

尝试输入其中一个保留的用户名“admin”、“manager”或“supervisor”。将闪现错误消息,如下所示:

Try entering one of the reserved usernames 'admin', 'manager', or 'supervisor'. The error message will be flashed as shown below −

weak password

此时,输入可接受的凭证并查看结果:

This time, enter acceptable credentials and see the result −

loggedin

Python Pyramid - Using SQLAlchemy

在本章中,我们将学习如何使用关系数据库作为 Pyramid Web 应用程序的后端。Python 可以通过相应的与 DB-API 兼容的连接器模块或驱动程序与几乎所有关系数据库进行交互。但是,我们将使用 @ {s9} 库作为 Python 代码和数据库之间的接口(我们将使用 SQLite 数据库,因为 Python 已经为其提供了内置支持)。SQLAlchemy 是一个流行的 SQL 工具包和对象关系映射器。

In this chapter, we shall learn how to use a relational database as a back-end with the Pyramid web application. Python can interact with almost every relational database using corresponding DB-API compatible connector modules or drivers. However, we shall use SQLAlchemy library as an interface between Python code and a database (we are going to use SQLite database as Python has in-built support for it). SQLAlchemy is a popular SQL toolkit and Object Relational Mapper.

对象关系映射是一种编程技术,用于在面向对象编程语言的不同类型系统之间转换数据。通常,像 Python 这种面向对象语言中使用的类型系统包含非标量类型。但是,大多数数据库产品的(例如 Oracle、MySQL 等)数据类型都是基本类型,例如整数和字符串。

Object Relational Mapping is a programming technique for converting data between incompatible type systems in object-oriented programming languages. Usually, the type system used in an Object Oriented language like Python contains non-scalar types. However, data types in most of the database products such as Oracle, MySQL, etc., are of primitive types such as integers and strings.

在 ORM 系统中,每个类都映射到底层数据库中的一个表。ORM 负责处理这些问题,让你专注于对系统逻辑进行编程,而不再需要自己编写乏味的数据库接口代码。

In an ORM system, each class maps to a table in the underlying database. Instead of writing tedious database interfacing code yourself, an ORM takes care of these issues for you while you can focus on programming the logics of the system.

为了使用 SQLALchemy,我们需要先使用 PIP 安装程序来安装该库。

In order to use SQLALchemy, we need to first install the library using PIP installer.

pip install sqlalchemy

SQLAlchemy 被设计为使用专为某个特定数据库而构建的 DBAPI 实现来运行。它使用方言系统与各种类型的 DBAPI 实现和数据库通信。所有方言都要求已安装相应的 DBAPI 驱动程序。

SQLAlchemy is designed to operate with a DBAPI implementation built for a particular database. It uses dialect system to communicate with various types of DBAPI implementations and databases. All dialects require that an appropriate DBAPI driver is installed.

以下是包含的方言:

The following are the dialects included −

  1. Firebird

  2. Microsoft SQL Server

  3. MySQL

  4. Oracle

  5. PostgreSQL

  6. SQLite

  7. Sybase

Database Engine

由于我们将使用 SQLite 数据库,因此我们需要为名为 @ {s10} 的数据库创建一个数据库引擎。从 @ {s12} 模块导入 @ {s11} 函数。

Since we are going to use SQLite database, we need to create a database engine for our database called test.db. Import create_engine() function from the sqlalchemy module.

from sqlalchemy import create_engine
from sqlalchemy.dialects.sqlite import *
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args = {"check_same_thread": False})

为了与数据库进行交互,我们需要获取其句柄。会话对象是数据库的句柄。使用 @ {s13} 定义会话类 - 一个可配置的会话工厂方法,绑定到引擎对象。

In order to interact with the database, we need to obtain its handle. A session object is the handle to database. Session class is defined using sessionmaker() - a configurable session factory method which is bound to the engine object.

from sqlalchemy.orm import sessionmaker, Session
session = sessionmaker(autocommit=False, autoflush=False, bind=engine)

接下来,我们需要一个声明式基类,用于在声明式系统中存储类的目录和映射的表。

Next, we need a declarative base class that stores a catalog of classes and mapped tables in the Declarative system.

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

Model Class

StudentsBase 的子类,映射到数据库中的 students 表。 Students 类中的属性对应于目标表中列的数据类型。请注意,id 属性对应于 book 表中的主键。

Students, a subclass of Base, is mapped to a students table in the database. Attributes in the Students class correspond to the data types of the columns in the target table. Note that the id attribute corresponds to the primary key in the book table.

class Students(Base):
   __tablename__ = 'student'
   id = Column(Integer, primary_key=True, nullable=False)
   name = Column(String(63), unique=True)
   marks = Column(Integer)
Base.metadata.create_all(bind=engine)

create_all() 方法在数据库中创建对应的表。可以使用 SQLite 可视化工具(如 SQLiteStudio. )进行确认

The create_all() method creates the corresponding tables in the database. It can be confirmed by using a SQLite Visual tool such as SQLiteStudio.

sqlitestudio

现在,我们为上述数据库中的 student 表定义视图函数,用于执行 CRUD(即添加、显示、修改和删除行)操作。

We shall now define view functions for performing CRUD operations (i.e. add, display, modify and delete rows) on the student table in the above database.

Add a New Student Record

首先,我们将创建一个 HTML 表单模板,以便用户输入学生数据并定义渲染该模板的视图。以下是 myform.html 模板

First, we shall create a HTML form template for the user to enter student data and define a view that renders the template. Here is the myform.html template

Example

<html>
<body>
   <form method="POST" action="http://localhost:6543/add">
   <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" value="Submit"> </p>
</body>
</html>

在 Pyramid 应用程序代码中,定义 index() 视图函数来渲染上述表单。

In the Pyramid application code, define the index() view function to render the above form.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config

@view_config(route_name='index', renderer='templates/myform.html')
def index(request):
   return {}

在应用程序配置中,将模式与该视图的 "/new" 模式一起注册,如下所示:

In the application configuration, register the route with the "/new" pattern for this view as −

if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('index', '/new')
      config.scan()
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()

由于上述模板中的 HTML 表单已提交给带 POST 动作的 /add URL,我们需要将该 URL 映射到 add 模式并注册 add() 视图,它将表单数据解析为 Students 类的一个对象。此对象已添加到数据库会话中,并且通过调用其 commit() 方法来完成操作。

As the HTML form in the above template is submitted to /add URL with POST action, we need to map this URL to add route and register add() view that parses the form data into an object of Students class. This object is added to the database session and the operation is finalized by calling its commit() method.

@view_config(route_name='add', request_method='POST')
def add(request):
   id=request.POST['id']
   name=request.POST['name']
   percent=int(request.POST['percent'])
   student=Students(id=id, name=name, percent=percent)
   session.add(student)
   session.commit()
   return HTTPFound(location='http://localhost:6543/')

确保在配置中添加了 add 模式,并将其映射到 /add URL 模式。

Make sure that the add route is added in the configuration, mapped to /add URL pattern.

config.add_route('add','/add')

Output

如果我们启动服务器并在浏览器中打开 http://localhost:6543/new ,则将显示录入表单,如下所示:

If we start the server and open http://localhost:6543/new in the browser, the Entry form will be displayed as follows −

student details

填写表单并按 "submit" 按钮。将调用 add() 视图,并在 students 表中添加新记录。重复此过程几次,以添加一些记录。以下是一些示例数据:

Fill the form and press the "submit" button. The add() view will be called and a new record will be added in the students table. Repeat the process a couple of times to add a few records. Here is a sample data −

student database

Show List of All Records

通过查询模型,可以获得 Students 模型的所有对象(对应于 students 表中的行)。

All the objects of the Students model (corresponding to row in students table) are obtained by querying the model.

rows = session.query(Students).all()

将每一行转换为一个 dict 对象,将所有这些对象追加到一个 dict 对象列表中,并作为上下文返回给 list.html 模板,以便以 HTML 模板的形式显示。showall() 视图函数会执行此过程,该视图函数与 list 模式相关联。

Each row is converted into a dict object, all of them are appended to a list of dict objects, and returned as a context to the list.html template to be displayed in the form of HTML template. The process is performed by the showall() view function, associated with list route.

@view_config(route_name='list', renderer='templates/marklist.html')
def showall(request):
   rows = session.query(Students).all()
   students=[]
   for row in rows:
      students.append({"id":row.id, "name":row.name, "percent":row.percent})
   return{'students':students}

Example

marklist.html 模板将 Students 列表呈现为 HTML 表。它的 HTML/jinja2 脚本如下:

The marklist.html template renders the Students list as a HTML table. Its HTML/jinja2 script is as follows −

<html>
<body>
<table border=1>
   <thead>
      <tr>
         <th>Student ID</th>
         <th>Student Name</th>
         <th>percentage</th>
         <th>Edit</th>
         <th>Delete</th>
      </tr>
   </thead>
   <tbody>
      {% for Student in students %}
         <tr>
         <td>{{ Student.id }}</td> <td>{{ Student.name }}</td>
         <td>{{ Student.percent }}</td>
         <td><a href="/show/{{ Student.id }}">edit</a></td>
         <td><a href="/delete/{{ Student.id }}">delete</a></td>
         </tr>
      {% endfor %}
   </tbody>
</table>
<h3><a href="http://localhost:6543/new">Add new</a></h3>
   </body>
</html>

在配置中添加 list 模式并使用 '/' URL 注册它。

Add the list route in the configuration and register it with '/' URL.

config.add_route('list', '/')

Output

启动服务器后,在浏览器中打开 http://localhost:6543/ 。将显示 students 表中现有记录的列表。

Open http://localhost:6543/ in the browser after starting the server. The list of existing records in the students table will be displayed.

add new

请注意,最后两列中的超链接。例如,“id=1”前的“edit”链接指向 http://localhost:6543/show/1 。这些链接用于执行更新和删除操作。

Notice the hyperlinks in the last two columns. For example, the "edit" link before "id=1" points to http://localhost:6543/show/1. These links are intended to execute update and delete operations.

Update Existing Record

在 /show/1 URL 中,有一个尾部路径参数。它映射到配置中的 'show' 模式。

In the /show/1 URL, there is a trailing path parameter. It is mapped to 'show' route in the configuration.

config.add_route('show', '/show/{id}')

该模式调用 show() 函数。它提取与给定 id 参数对应的记录,用其内容填充 HTML 表单,并允许用户更新 name 和/或 percent 字段的值。

This route invokes the show() function. It fetches the record corresponding to the given id parameter, populates the HTML form with its contents and lets the user to update values of name and/or percent fields.

@view_config(route_name='show', renderer='templates/showform.html')
def show(request):
   id=request.matchdict['id']
   row = session.query(Students).filter(Students.id == id).first()
   student={'id':row.id, 'name':row.name, 'percent':row.percent}
   return {'student':student}

Example

showform.html 模板的 HTML/jinja2 代码如下:

The HTML/jinja2 code of showform.html template is as follows −

<html>
<body>
   <form method="POST" action="http://localhost:6543/update">
   <p>Student Id: <input type="text" name="id" value="{{ student.id }} " readonly/> </p>
   <p>student Name: <input type="text" name="name" value="{{ student.name }}"/> </p>
   <p>Percentage: <input type="text" name="percent" value="{{ student.percent }}"/> </p>
   <p><input type="submit" value="Submit"> </p>
</body>
</html>

Output

让我们使用 id=3 更新记录。单击相应的编辑链接以导航到 http://localhost:6543/show/3

Let us update the record with id=3. Click on corresponding Edit link to navigate to http://localhost:6543/show/3

percentage

更改分数文本字段中的值并按提交。该表单被重定向到 /update URL,且它调用了 update() 视图。它获取所提交的数据并更新相应的对象,从而也更新了学生表中的底层行。

Change the value in marks text field and press submit. The form is redirected to /update URL and it invokes update() view. It fetches the submitted data and updates the corresponding object thereby the underlying row in students table is also updated.

@view_config(route_name='update', request_method='POST')
def update(request):
   id=int(request.POST['id'])
   student = session.query(Students).filter(Students.id == id).first()
   student.percent=int(request.POST['percent'])
   session.commit()
   return HTTPFound(location='http://localhost:6543/')

返回语句将浏览器重定向回 “/” URL,该 URL 指向 list() 函数并显示更新的分数表。

The return statement redirects the browser back to the '/' URL, which points to the list() function and shows the updated marklist.

updated marklist

确保在运行之前已添加 update 路由至配置。

Make sure that the update route as added to the configuration before running.

config.add_route('update', '/update')

Delete a Record

要删除与分数表中的行相对应的记录,请按照最后一列中的删除链接进行操作。例如,单击第 3 行中的删除将发出 http://localhost:6543/delete/3 URL 并调用以下视图函数:

To delete a record corresponding to a row in the marklist table, follow the Delete link in the last column. For example, clicking on Delete in 3rd row emits http://localhost:6543/delete/3 URL and invokes following view function −

@view_config(route_name='delete', renderer='templates/deleted.html')
def delete(request):
   id=request.matchdict['id']
   row = session.query(Students).filter(Students.id == id).delete()
   return {'message':'Redcord has been deleted'}

Example

从 URL 中解析的路径参数所对应的对象将被删除,且适当的消息将由以下模板 - deleted.html 呈现:

The object corresponding to the path parameter parsed from the URL is deleted and the appropriate message is rendered by the following template - deleted.html

<html>
<body>
   <h3>{{ message}}</h3>
   <br><br>
   <a href="http://localhost:6543/">Click here to refresh the mark list</a>
</body>
</html>

显然,必须在应用程序配置注册表中添加 delete 路由。

Obviously, the delete route has to be added in the application config registry.

config.add_route('delete', '/delete/{id}')

Output

记录删除操作的结果如下所示:

The result of record delete action is as shown below −

record

采取以下步骤来执行上述解释的活动:

Take the following steps to perform the above explained activity −

  1. Create a folder named as testapp in the Pyramid virtual environment

  2. Inside testapp, create the templates folder.

  3. Create a blank init.py inside testapp so that it becomes a package.

  4. Put marklist.html, myform.html, showform.html and deleted.html files in "testapp\templates" folder. Codes of these files have been given above.

  5. Save the following code as models.py in testapp.

from sqlalchemy.dialects.sqlite import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import Session
from sqlalchemy import Column, Integer, String
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"

Base = declarative_base()

class Students(Base):
      __tablename__ = 'student'
   id = Column(Integer, primary_key=True, nullable=False)
   name = Column(String(63), unique=True)
   percent = Column(Integer)

def getsession():
   engine = create_engine(
      SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
   )
   Base.metadata.create_all(bind=engine)
   Session = sessionmaker(bind = engine)
   session = Session()
   return session
  1. Save the following code as views.py in testapp folder.

from pyramid.response import Response
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from models import Students
from main import session

@view_config(route_name='list', renderer='templates/marklist.html')
def showall(request):
   rows = session.query(Students).all()
   students=[]
   for row in rows:
      students.append({"id":row.id, "name":row.name, "percent":row.percent})
      return{'students':students}

@view_config(route_name='index', renderer='templates/myform.html')
def index(request):
   return {}

@view_config(route_name='add', request_method='POST')
def add(request):
   id=request.POST['id']
   name=request.POST['name']
   percent=int(request.POST['percent'])
   student=Students(id=id, name=name, percent=percent)
   session.add(student)
   session.commit()
   return HTTPFound(location='http://localhost:6543/')

@view_config(route_name='update', request_method='POST')
def update(request):
   id=int(request.POST['id'])
   student = session.query(Students).filter(Students.id == id).first()
   student.percent=int(request.POST['percent'])
   session.commit()
   return HTTPFound(location='http://localhost:6543/')

@view_config(route_name='show', renderer='templates/showform.html')
def show(request):
   id=request.matchdict['id']
   row = session.query(Students).filter(Students.id == id).first()
   student={'id':row.id, 'name':row.name, 'percent':row.percent}
   return {'student':student}

@view_config(route_name='delete', renderer='templates/deleted.html')
def delete(request):
   id=request.matchdict['id']
   row = session.query(Students).filter(Students.id == id).delete()
   return {'message':'Redcord has been deleted'}
  1. Save the following code as main.py in testapp folder.

from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from models import getsession
session=getsession()

if __name__ == '__main__':
   with Configurator() as config:
      config.include('pyramid_jinja2')
      config.add_jinja2_renderer(".html")
      config.add_route('list', '/')
      config.add_route('index', '/new')
      config.add_route('add','/add')
      config.add_route('show', '/show/{id}')
      config.add_route('update', '/update')
      config.add_route('delete', '/delete/{id}')
      config.scan('testapp')
      app = config.make_wsgi_app()
   server = make_server('0.0.0.0', 6543, app)
   server.serve_forever()
  1. Run main.py from the command prompt.

Python main.py
  1. Use http://localhost:6543/ URL in the browser window. A table with only the headings and no records will be displayed.

  2. Follow Add new link below the table to add records.

  3. Click the "Edit" link in the table to update a record.

  4. Clink the "Delete" link in the table to delete selected record.

Python Pyramid - Cookiecutter

到目前为止,我们已通过手动执行路由配置、添加视图和使用模板构建了 Pyramid 应用程序。 Cookiecutter 提供了一种生成 Pyramid 项目结构的便捷替代方法。它是一个命令行实用程序,使用某些预定义的项目模板。然后可以对项目进行微调,以适应用户可能具有的特定要求。

We have so far built the Pyramid application by manually performing the route configuration, adding the views and using the templates. Cookiecutter offers a convenient alternative to generate a Pyramid project structure. It is a command-line utility that uses certain predefined project templates. The project can then be fine-tuned to accommodate specific requirements that the user may have.

Cookiecutter 创建的 Python 项目是 Python 包。可以进一步定制默认的应用程序逻辑。如此创建的项目结构极其可扩展,易于分发。

The Python project created by Cookiecutter is a Python package. The default application logic can be further customized. The project structure so created is extremely extensible and is easy to distribute.

Cookiecutter 实用程序由 Audrey Feldroy 开发。它适用于 Python >=3.7 版本。可以利用 Python、JavaScript、Ruby、CoffeeScript、RST、Markdown、CSS、HTML 语言或脚本在项目模板中生成项目。Github 托管了许多预建的项目模板,可以使用其中的任何一个。

The Cookiecutter utility is developed by Audrey Feldroy. It works on Python versions >=3.7. The project templates in Python, JavaScript, Ruby, CoffeeScript, languages or RST, Markdown, CSS, HTML scripts can be used to generate a project. Github hosts a number of pre-built project templates, any of which can be used.

由 cookiecutter 模板生成的项目是一个跨平台包。Cookiecutter 项目生成完全自动化,你不必为此编写任何代码。Cookiecutter 命令一经调用,它会读取正在使用的模板,并提示用户为设置参数选择适当的值。首先,使用 PIP 安装程序安装 Cookiecutter。

The project built from cookiecutter template is a cross-platform package. Cookiecutter project generation is completely automated and you don’t have to write any code for it. Once the cookiecutter command is invoked, it reads the template being used and prompts the user to choose appropriate values for the settings parameters. To begin with, install Cookiecutter with PIP installer.

pip install cookiecutter

若要验证是否正确安装了 Cookiecutter,请运行

To verify if Cookiecutter is correctly installed, run

>>> import cookiecutter
>>> cookiecutter.__version__
'1.7.3'

Python Pyramid - Creating A Project

假定 Pyramid 虚拟环境已启动并运行,并且 Cookiecutter 已安装在其中。创建 Cookiecutter 项目最简单的方法是使用预构建的启动模板,如下命令所示:

It is assumed that a Pyramid virtual environment is up and running, and Cookiecutter is installed in it. The easiest way to create a Cookiecutter project is to use a pre-built starter template as per the following command −

cookiecutter gh:Pylons/pyramid-cookiecutter-starter --checkout 2.0-branch

模板下载后,会询问用户选择何种项目名称 −

The template is downloaded and the user is asked about his choice of name of the project −

project_name [Pyramid Scaffold]: testproj
repo_name [testproj]:

接下来,选择模板语言。

Then choose the template language.

选择 template_language

Select template_language

1 - jinja2
2 - chameleon
3 - mako
Choose from 1, 2, 3 [1]: 1

既然我们熟悉 jinja2,请选择 1。接下来,使用 SQLALchemy 作为后端。

Since we are familiar with jinja2, give 1 as the choice. Next, use SQLALchemy as the backend.

Select backend:
1 - none
2 - sqlalchemy
3 - zodb
Choose from 1, 2, 3 [1]: 2

testproj 文件夹中,创建以下文件结构 −

Inside the testproj folder, following file structure is created −

│ development.ini
│ MANIFEST.in
│ production.ini
│ pytest.ini
│ README.txt
│ setup.py
│ testing.ini
│
├───testproj
│ │ pshell.py
│ │ routes.py
│ │ __init__.py
│ │
│ ├───alembic
│ │ │ env.py
│ │ │ script.py.mako
│ │ │
│ │ └───versions
│ │ README.txt
│ │
│ ├───models
│ │ meta.py
│ │ mymodel.py
│ │ __init__.py
│ │
│ ├───scripts
│ │ initialize_db.py
│ │ __init__.py
│ │
│ ├───static
│ │ pyramid-16x16.png
│ │ pyramid.png
│ │ theme.css
│ │
│ ├───templates
│ │ 404.jinja2
│ │ layout.jinja2
│ │ mytemplate.jinja2
│ │
│ └───views
│ default.py
│ notfound.py
│ __init__.py
│
└───tests
    conftest.py
    test_functional.py
    test_views.py
    __init__.py

外部 testproj 文件夹有一个内部 testproj 包子文件夹和 test 包。内部 testproj 子文件夹是一个具有模型、脚本、子包以及静态和模板文件夹的包。

The outer testproj folder has an inner testproj package subfolder and tests package. The inner testproj subfolder is a package having models and scripts, subpackages, and static as well as templates folders.

接下来,使用 Alembic 初始化和升级数据库。

Next, initialize and upgrade the database using Alembic.

# Generate your first revision.
alembic -c development.ini revision --autogenerate -m "init"
# Upgrade to that revision.
alembic -c development.ini upgrade head

Alembic 是一个轻量级数据库迁移工具,可与 Python 的 SQLAlchemy 数据库工具包一起使用。外部项目文件夹现在将显示 testproj.sqlite 数据库。

Alembic is a lightweight database migration tool for usage with the SQLAlchemy Database Toolkit for Python. The outer project folder will now show a testproj.sqlite database.

development.ini 文件为数据库提供了默认数据。通过以下命令使用它填充数据库。

The development.ini file provides a default data for the database. Populate the database with it by the following command.

initialize_testproj_db development.ini

Cookiecutter 实用程序还生成 test 包中的测试套件。它们基于 PyTest 包。继续并查看测试是否通过。

The Cookiecutter utility also generates the test suite in the tests package. They are based on PyTest package. Go ahead and see if the tests pass.

Pytest
================ test session starts ======================
platform win32 -- Python 3.10.1, pytest-7.1.2, pluggy-1.0.0
rootdir: F:\pyram-env\testproj, configfile: pytest.ini, testpaths: testproj, tests
plugins: cov-3.0.0
collected 5 items

tests\test_functional.py .. [ 40%]
tests\test_views.py ... [100%]
=============== 5 passed, 20 warnings in 6.66s ===============

Cookiecutter 使用 Waitress 服务器。Pyramid 应用程序在 localhost 的端口 6543 上通过以下命令提供服务 −

Cookiecutter uses the Waitress server. The Pyramid application is served on localhost’s port 6543 by following command −

pserve development.ini
Starting server in PID 67700.
2022-06-19 23:43:51,308 INFO [waitress:485][MainThread] Serving on http://[::1]:6543
2022-06-19 23:43:51,308 INFO [waitress:485][MainThread] Serving on http://127.0.0.1:6543

打开浏览器并访问其中的 http://localhost:6543/ 。新创建的项目的主页将显示如下 −

Open the browser and visit http://localhost:6543/ in it. The homepage of the newly created project will be displayed as follows −

cookiecutter

Debug Toolbar

您可以在主页的右上角找到一个小一些的 Pyramid 标志。单击它以打开一个新选项卡和一个调试工具栏,该工具栏提供有关项目的大量有用信息。

You can find a smaller Pyramid logo at the top right of the homepage. Click on it to open a new tab and a debug toolbar that provides lots of useful information about the project.

例如,历史记录标题下的 SQLAlchemy 选项卡显示 SQLAlchemy 查询,显示从 development.ini 中的默认数据创建的模型的结构。

For example, the SQLAlchemy tab under the history heading shows the SQLAlchemy queries showing the structure of the model created from the default data in development.ini.

pyramid  logo

全局标题再次显示诸如 Introspection、Routes 等选项卡,如下所示。单击“路由”选项卡以查看应用程序配置中定义的路由及其匹配模式。

The Global heading again shows tabs such as Introspection, Routes, etc. as shown below. Click the "Routes" tab to see the routes and their matching patterns defined in the application’s configuration.

debug toolbar

Python Pyramid - Project Structure

如前所述,外部 testproj 文件夹包含 testproj 和 test 包。此外,它还有其他用于描述、运行和测试应用程序的文件。这些文件是 −

As mentioned earlier, the outer testproj folder contains testproj and test packages. In addition, it has other files used to describe, run, and test the application. These files are −

  1. MANIFEST.in contains list of files to be included in a source distribution of the package.

  2. development.ini is a PasteDeploy configuration file that can be used to execute your application during development.

  3. production.ini is a PasteDeploy configuration file that can be used to execute your application in a production configuration.

  4. pytest.ini is a configuration file for running tests.

  5. setup.py is the standard Setuptools setup.py file used to test and distribute the application.

  6. testing.ini is a configuration file used to execute the application’s tests.

".ini" 文件是 Cookiecutter 实用程序用来生成 Pyramid 应用程序结构的配置。这些文件使用一个称为 PasteDeploy 的系统,该系统由 Ian Bicking 开发。此库会随 Pyramid 自动安装。

The ".ini" files are the configurations used by Cookiecutter utility to generate the Pyramid application structure. These filesuse a system called PasteDeploy, which has been developed by Ian Bicking. This library is installed automatically along with Pyramid.

尽管可以在没有 PasteDeploy 支持的情况下开发 Pyramid 应用程序,但它提供了一种启动、调试和测试应用程序的标准化方法。

Although a Pyramid application can be developed without PasteDeploy support, it gives a standardized way of starting, debugging and testing the application.

预定义的设置从配置文件中读取(带有 .ini 扩展名)。这些文件主要包含应用程序配置设置、服务器设置和记录设置。

The predefined settings are read from the configuration files (with .ini extension). These files contain mainly the application configuration settings, server settings and logging settings.

development.ini

如前所示,使用 Cookiecutter 构建的 Pyramid 应用程序由以下命令调用 −

As shown earlier, the Pyramid application built with Cookiecutter is invoked by the following command −

pserve development.ini

development.ini 中包含应用程序的 PasteDeploy 配置规范。该文件中的配置规范包含各种部分,如 [app:main]、[server:main]、[loggers] 等。

The development.ini contains the PasteDeploy configuration specifications of the application. The configuration specifications in this file are having various sections such as [app:main], [server:main], [loggers] etc.

最重要的部分是 [app:main]。它指定应用程序的起始点。

The most important section id [app:main]. It specifies the starting point of the application.

[app:main]
use = egg:testproj

pyramid.reload_templates = true
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
pyramid.includes = pyramid_debugtoolbar

sqlalchemy.url = sqlite:///%(here)s/testproj.sqlite

retry.attempts = 3

第一个条目“use = egg:testproj”表示 Pyramid WSGI 应用程序对象主函数的名称。它在 textproj 包的 init.py 文件(位于 testproj 项目文件夹中)中声明。此部分包含其它启动时间配置设置。

The very first entry "use = egg:testproj" indicates the name of the Pyramid WSGI application object main. It is declared in the init.py file of the textproj package (inside the testproj project folder). This section contains other startup time configuration settings.

例如,“pyramid.includes”设置指定在运行时要包含的包。在上述示例中,包含 debugtoolbar 包,以便在单击 Pyramid 徽标时激活调试面板。我们在前一节中已经了解它的作用。

For instance, the "pyramid.includes" setting specifies the packages to be included in the runtime. In the above example, the debugtoolbar package is included so that the debug panel gets activated when the Pyramid logo is clicked. We have seen its functioning in the earlier section.

我们还会看到,此应用程序中要使用的数据库的 URL 也已指定。

We also see that the URL of the database to be used in this application has also been specified.

[server:main] 部分指定监听在 TCP 端口 6543 上的 WSGI 服务器的配置。它配置为仅监听 localhost (127.0.0.1)。

The [server:main] section specifies the configuration of a WSGI server which listens on TCP port 6543. It is configured to listen on localhost only (127.0.0.1).

[server:main]
use = egg:waitress#main
listen = localhost:6543

其它各种与日志记录相关的部分使用 Python 的日志记录库。这些“.ini”文件部分传递给日志记录模块的配置文件配置引擎。

Other various logging related sections use Python’s logging library. These ".ini" file sections are passed to the logging module’s config file configuration engine.

production.ini

当应用程序在生产模式下部署时,此文件用于提供应用程序,而不是“development.ini”。这两个文件类似。但是,“production.ini”中禁用了调试工具栏,禁用了重新加载选项,并关闭了调试选项。

This file used to serve the application instead of the "development.ini" when the application is deployed in the production mode. Both these files are similar. However, in "production.ini", the debug toolbar is disabled, the reload options are disabled and turns off the debugging options.

下面是一个经过精简的典型“production.ini”文件版本 −

Here’s a stripped-down version of typical "production.ini" file −

[app:main]
use = egg:testproj
pyramid.reload_templates = false
pyramid.debug_authorization = false
pyramid.debug_notfound = false
pyramid.debug_routematch = false
pyramid.default_locale_name = en
sqlalchemy.url = sqlite:///%(here)s/testproj.sqlite
retry.attempts = 3
[pshell]
setup = testproj.pshell.setup
[alembic]
script_location = testproj/alembic
file_template = %%(year)d%%(month).2d%%(day).2d_%%(rev)s
[server:main]
use = egg:waitress#main
listen = *:6543
[loggers]
keys = root, testproj, sqlalchemy, alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
[logger_testproj]
level = WARN
handlers =
qualname = testproj
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = WARN
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s:%(lineno)s][%(threadName)s] %(message)s

Python Pyramid - Package Structure

Cookiecutter 实用工具会自动在同名的父项目文件夹内创建一个包文件夹。包文件夹包括以下文件和子文件夹。

The Cookiecutter utility automatically creates a package folder inside the parent project folder of the same name. The package folder consists of the following files and subfolders.

init.py

一个文件夹需要 init.py 文件,才能被视为一个 Python 包。 testproj 包也有这个文件,它本质上为 Pyramid WSGI 应用程序项目声明了 development.ini 以将其用作入口点。

A folder needs init.py file for it to be treated as a Python package. The testproj package also has this file, which essentially declares the Pyramid WSGI application project for the development.ini to use it as the entry point.

main() 函数返回应用程序对象。它通过包括在运行 cookiecutter 时选择的模板库,包括 routes 模块,并通过扫描现有包来将视图添加到配置器,配置应用程序注册表。以下 Python 代码作为 init.py 文件自动生成。

The application object is returned by the main() function. It configures the application registry by including the template library chosen at the time of running cookiecutter, including the routes module and adding the views to the configurator by scanning the existing package. Following Python code is auto generated as init.py file.

from pyramid.config import Configurator
def main(global_config, **settings):
   """ This function returns a Pyramid WSGI application.
   """
   with Configurator(settings=settings) as config:
      config.include('pyramid_jinja2')
      config.include('.routes')
      config.include('.models')
      config.scan()
   return config.make_wsgi_app()

routes.py

Cookiecutter 实用工具会自动生成一个 Python 脚本,它有一个名为 includeme() 的函数。它添加一个静态路由和指向 '/' URL 模式的主页路由。

The Cookiecutter utility automatically generates a Python script having a function called includeme(). It adds a static route and a home route pointing to '/' URL pattern.

def includeme(config):
   config.add_static_view('static', 'static', cache_max_age=3600)
   config.add_route('home', '/')

这些路由通过上面介绍的 init.py 文件中的 main() 函数添加到应用程序配置。

These routes are added to the application configuration by the main() function in init.py file explained above.

Views Package

项目包(在我们的例子中为 testproj *package) contains this views subpackage - a folder containing a blank *init.py ,一个名为 default.py 的 Python 模块,其中包含名为 my_view() 的视图函数定义。)它将项目名称作为上下文发送到预构建模板 mytemplate.jinja2

The project package (in our case testproj *package) contains this views subpackage - a folder containing a blank *init.py, a Python module called default.py that contains definition a view function named my_view(). It sends the name of the project as a context to a pre-built template mytemplate.jinja2

from pyramid.view import view_config
from pyramid.response import Response
from sqlalchemy.exc import SQLAlchemyError
from .. import models

@view_config(route_name='home', renderer='testproj:templates/mytemplate.jinja2')
def my_view(request):
   try:
      query = request.dbsession.query(models.MyModel)
      one = query.filter(models.MyModel.name == 'one').one()
   except SQLAlchemyError:
      return Response(db_err_msg, content_type='text/plain', status=500)
   return {'one': one, 'project': 'testproj'}

db_err_msg = """\
Pyramid is having a problem using your SQL database.
....
"""

default.py 脚本还导入了模型子包中 mymodel 的定义。此视图包还在 notfound.py 文件中定义了 notfound 视图。

The default.py scripts also imports definition of mymodel in models subpackage. This views package also defines a notfound view in notfound.py file.

from pyramid.view import notfound_view_config
@notfound_view_config(renderer='testproj:templates/404.jinja2')
def notfound_view(request):
   request.response.status = 404
   return {}

Static Folder

testproj 包文件夹下的此文件夹包含 Pyramid logo 文件和针对主页的 theme.CSS。

This folder under the testproj package folder contains Pyramid logo files and theme.CSS for the homepage.

Templates Folder

我们知道,Web 模板需要存储在 templates 文件夹中。此子文件夹包含 jinja2 模板。我们有一个名为 layout.jinja2 的基本模板, mytemplate.jinja2 继承了它,然后由 my_view() 视图函数渲染。

We know that the web templates need to be stored in templates folder. This subfolder contains jinja2 templates. Here we have a base template named as layout.jinja2 and it is inherited by mytemplate.jinja2 to be rendered by my_view() view function.

{% extends "layout.jinja2" %}

{% block content %}
<div class="content">
   <h1><span class="font-semi-bold">Pyramid</span> <span class="smaller">Starter project</span></h1>
   <p class="lead">Welcome to <span class="font-normal">{{project}}</span>, a Pyramid application generated by<br><span class="font-normal">Cookiecutter</span>.</p>
</div>
{% endblock content %}

Models Package

tesptproj 包文件夹下的此子包包含 mymodel.py ,其中具有名为 MyModel 的 SQLAlchemy 模型的定义。

This subpackage under the tesptproj package folder holds mymodel.py that has the definition of SQLAlchemy model named as MyModel.

from sqlalchemy import (
   Column,
   Index,
   Integer,
   Text,
)

from .meta import Base
class MyModel(Base):
   __tablename__ = 'models'
   id = Column(Integer, primary_key=True)
   name = Column(Text)
   value = Column(Integer)
Index('my_index', MyModel.name, unique=True, mysql_length=255)

meta.py 在 SQLAlchemy 中声明了一个 Declarative Base 类对象。

The meta.py declares an object of Declarative Base class in SQLAlchemy.

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import MetaData

NAMING_CONVENTION = {
   "ix": "ix_%(column_0_label)s",
   "uq": "uq_%(table_name)s_%(column_0_name)s",
   "ck": "ck_%(table_name)s_%(constraint_name)s",
   "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
   "pk": "pk_%(table_name)s"
}
metadata = MetaData(naming_convention=NAMING_CONVENTION)
Base = declarative_base(metadata=metadata)

Python Pyramid - Creating A Project Manually

Cookiecutter 工具使用预定义的项目模板自动生成项目和包结构。对于复杂项目,它在正确组织各种项目组件方面节省了大量的手动工作。

The Cookiecutter utility uses pre-defined project templates to auto-generate the project and package structure. For complex projects, it saves a lot of manual effort in properly organizing various project components.

但是,可以手动构建 Pyramid 项目,而不必使用 Cookiecutter。在本节中,我们将看到如何按照以下简单步骤构建名为 Hello 的 Pyramid 项目。

However, a Pyramid project can be built manually without having to use Cookiecutter. In this section, we shall see how a Pyramid project named Hello is built in following easy steps.

setup.py

在 Pyramid 虚拟环境中创建项目目录。

Create a project directory within Pyramid virtual environment.

md hello
cd hello

并将以下脚本另存为 setup.py

And save the following script as setup.py

from setuptools import setup

requires = [
   'pyramid',
   'waitress',
]
setup(
   name='hello',
   install_requires=requires,
   entry_points={
      'paste.app_factory': [
         'main = hello:main'
      ],
   },
)

如前所述,这是一个 Setuptools 设置文件,它定义了为软件包安装依赖项的要求。

As mentioned earlier, this is a Setuptools setup file that defines requirements for installing dependencies for your package.

运行以下命令来安装项目并在名称 hello.egg-info. 中生成“egg”

Run the following command to install the project and generate the 'egg' in the name hello.egg-info.

pip3 install -e.

development.ini

Pyramid 主要使用 PasteDeploy 配置文件来指定主应用程序对象和服务器配置。我们准备在 hello 包的 egg 信息和侦听 localhost 5643 端口的 Waitress 服务器中使用应用程序对象。因此,将以下代码段另存为 development.ini 文件。

Pyramid uses PasteDeploy configuration file mainly to specify the main application object, and the server configuration. We are going to use the application object in the egg info of hello package, and the Waitress server, listening on port 5643 of the localhost. Hence, save the following snippet as development.ini file.

[app:main]
use = egg:hello

[server:main]
use = egg:waitress#main
listen = localhost:6543

init.py

最后,应用程序代码位于此文件中,此文件对于识别 hello 文件夹为一个包至关重要。

Finally, the application code resides in this file which is also essential for the hello folder to be recognised as a package.

该代码是一个基本的 Hello World Pyramid 应用程序代码,具有 hello_world() 视图。 main() 函数将此视图注册到具有 '/' URL 模式的 hello 路由,并返回由 make_wsgi_app() 给出的 Configurator 方法的应用程序对象。

The code is a basic Hello World Pyramid application code having hello_world() view. The main() function registers this view with hello route having '/' URL pattern, and returns the application object given by make_wsgi_app() method of Configurator.

from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
   return Response('<body><h1>Hello World!</h1></body>')
def main(global_config, **settings):
   config = Configurator(settings=settings)
   config.add_route('hello', '/')
   config.add_view(hello_world, route_name='hello')
   return config.make_wsgi_app()

最后,使用 pserve 命令提供该应用程序。

Finally, serve the application with the help of pserve command.

pserve development.ini --reload

Python Pyramid - Command Line Pyramid

Pyramid 库有一个 scripts 子包,它包含许多 Python 脚本,这些脚本可用于控制和检查 Pyramid 应用程序。这些模块既可以用作可导入模块,也可用于命令提示符。因此,它们通常被称为命令行脚本。

The Pyramid library has a scripts subpackage, and it contains a number of Python scripts that are made available to control and inspect a Pyramid application. These modules can be used both as an importable module as well as from command prompt. Hence, they are often called as command line scripts.

这些命令行脚本是-

These command line scripts are −

  1. pserve − serves a web application that uses a PasteDeploy configuration file.

  2. pviews − Displaying Matching Views for a Given URL.

  3. pshell − The Interactive Shell.

  4. proutes − Displaying All Application Routes.

  5. ptweens − Displaying "Tweens".

  6. prequest − Invoking a Request.

  7. pdistreport − Showing All Installed Distributions and Their Versions.

所有这些命令行脚本都使用 PasteDeploy 配置文件 (development.ini)。

All these command line scripts use the PasteDeploy configuration file (development.ini).

pserve

这是最重要的脚本。在“development.ini”[app:main] 部分配置的 Pyramid 应用程序借助所选服务器(Waitress)和提到的主机和端口(localhost:6543)提供服务。

This is the most important script. The Pyramid application configured in the "development.ini" [app:main] section is served with the help of the chosen server (Waitress) and the mentioned host and port (localhost:6543).

假设 Pyramid 项目 (testproj) 在 Pyramid 虚拟环境中创建在同名的文件夹中,以下命令开始侦听传入的浏览器请求-

Assuming that the Pyramid project (testproj) is created in the folder of the same name in the Pyramid virtual environment, the following command starts listening to incoming browser requests −

Env>..\scripts\pserve development.ini

pserve 模块(以及其他 Pyramid 命令行脚本)可以用作命令提示符中 Python 解释器的参数运行。

The pserve module (as also the other Pyramid command-line scripts) can be run as an argument of Python interpreter in the command prompt.

Env>python -m pyramid.scripts.pserve development.ini
Starting server in PID 1716.
2022-06-23 14:13:51,492 INFO [waitress:485][MainThread] Serving on http://[::1]:6543
2022-06-23 14:13:51,492 INFO [waitress:485][MainThread] Serving on http://127.0.0.1:6543

为使 pserve 实用程序更灵活,可以使用以下命令行参数-

To make pserve utility more flexible, the following command line parameters can be used −

  1. config_uri − The URI to the configuration file.

  2. -n <name> − Load the named application (default main).

  3. -s <server_type> − Use the named server.

  4. --server-name <section_name> − Use the named server as defined in the configuration file (default: main)

  5. --reload − Use auto-restart file monitor.

  6. -b − Open a web browser to the server url.

应用程序提供于 http://localhost:6543 的情况下,访问将受到限制,以致于只有运行在同一机器上的浏览器才能访问。如果您允许网络上其他机器访问,请编辑“development.ini”文件,然后像下面所示替换 [server:main] 部分中的侦听值 −

The application is served at http://localhost:6543 in which case, the access is restricted such that only a browser running on the same machine. If you want to let the other machines on the same network, then edit the "development.ini" file, and replace the listen value in the [server:main] section as shown below −

[server:main]
use = egg:waitress#main
listen = *:6543

设置 *:6543 等于 0.0.0.0:6543 [::]:6543,结果,应用程序响应系统拥有的所有 IP 地址上的请求,而不仅仅是响应 localhost 的请求。

The setting *:6543 is equivalent to 0.0.0.0:6543 [::]:6543, as a result, the application responds to requests on all IP addresses possessed by your system, not just requests to localhost.

pserve 命令行中的 --reload 选项会在运行代码修改时自动重新加载应用程序。

The --reload option in the pserve command line causes the application to be reloaded automatically whenever the running code is modified.

使用 --reload option. 启动应用程序

Start the application with --reload option.

pserve development.ini --reload
Starting monitor for PID 36224.
Starting server in PID 36224.
Serving on http://localhost:6543
Serving on http://localhost:6543

如果对项目的 .py 文件或 .ini 文件进行了任何更改,服务器会自动重启 −

If any change to the project’s .py files or .ini files is made, the server restart automatically −

testproj/development.ini changed; reloading ...
Gracefully killing the server.
Starting monitor for PID 36286.
Starting server in PID 36286.
Serving on http://localhost:6543
Serving on http://localhost:6543

pviews

pviews 命令行脚本用在命令终端窗口中,为给定 URL 匹配的路由和视图打印摘要。 pviews 命令接受两个参数。第一个参数是应用程序的“ini”文件和内置节的路径。格式应为 config_file#section_name (默认值为 main)。第二个参数是要测试匹配视图的 URL。

The pviews command line script is used in the command terminal window to print a summary of matching routes and views for a given URL. The pviews command accepts two arguments. The first argument is the path to your application’s ".ini" file and section name inside it. This should be of the format config_file#section_name (default value is main). The second argument is the URL to test for matching views.

让我们在先前使用 Cookiecutter 构建的 testproj 项目中使用 development.ini 文件执行 pviews 命令。

Let us pviews command with the development.ini file in our testproj project built earlier with Cookiecutter.

Env>..\scripts\pviews development.ini /
URL = /
   context: <pyramid.traversal.DefaultRootFactory object at 0x000001DD39BF1DE0>
   view name:
   Route:
   ------
   route name: home
   route pattern: /
   route path: /
   subpath:

      View:
      -----
      testproj.views.default.my_view

输出显示请求的 URL,其下方显示所有匹配的视图及其视图配置详细信息。此示例中只有一个视图匹配,因此只有一个视图部分。

The output shows the requested URL at the top and below which all the matching views are displayed with their view configuration details. In this example only one view matches, so there is just a single View section.

pshell

pshell 脚本使得可以用 Python 提示符与金字塔应用程序的环境进行交互。此 shell 使用 PasteDeploy 配置文件(如 development.ini)作为命令行参数(类似于其他金字塔脚本),并打开 Python 交互式 shell。

The pshell script makes it possible to interact with the Pyramid application’s environment from Python prompt. This shell uses the PasteDeploy configuration file i.e. development.ini as a command line argument (like the other Pyramid scripts) and opens up Python interactive shell.

Env>..\scripts\pshell development.ini
Python 3.10.1 (tags/v3.10.1:2cd268a, Dec 6 2021, 19:10:37) [MSC v.1929 64 bit (AMD64)] on win32
Type "help" for more information.

Environment:
   app                    The WSGI application.
   dbsession              <sqlalchemy.orm.session.Session object at 0x0000020E9F1452D0>
   models                 <module 'testproj.models' from 'f:\\pyram-env\\testproj\\testproj\\models\\__init__.py'>
   registry               Active Pyramid registry.
   request                Active request object.
   root                   Root of the default resource tree.
   root_factory           Default root factory used to create `root`.
   tm                     Single-thread implementation of `~transaction.interfaces.ITransactionManager`.

>>>

脚本将读取配置,并将其声明的对象作为 Python 对象用于交互。我们能够检查 Python 提示符中的其行为。

The script reads the configuration and the objects declared in it are made available as Python objects to interact with. We can inspect their behaviour from the Python prompt.

>>> root
<pyramid.traversal.DefaultRootFactory object at 0x0000020E9E2507F0>
>>> registry
<Registry testproj>

将注册表设置从“development.ini”读入词典。我们可以使用 for 循环遍历其内容 −

The registry settings are read from "development.ini" into a dictionary. We can traverse its contents using the for loop −

>>> for k,v in registry.settings.items():
... print (k,":",v)
...
pyramid.reload_templates : True
pyramid.debug_authorization : False
pyramid.debug_notfound : False
pyramid.debug_routematch : False
pyramid.default_locale_name : en
pyramid.includes :
pyramid_debugtoolbar
sqlalchemy.url : sqlite:///…\testproj/testproj.sqlite
retry.attempts : 3
tm.manager_hook : <function explicit_manager at 0x000001D9E64E4550>

甚至有可能借助模型中的 SQLAlchemy 模块与数据库进行交互。

It is even possible to interact with the database with the help of SQLAlchemy model declared in models.py.

当我们首次完成 cookiecutter 步骤时,应用程序数据库最初被初始化。我们在“testproj.sqlite”数据库中发现一个 models 表格,其中有一个记录。

The application database is initialized in the beginning when we first complete the cookiecutter steps. We find a models table in the "testproj.sqlite" database with one record in it.

testproj

现在我们按照以下内容从 Python 提示符访问此表格 −

We now access this table from the Python prompt as under −

>>> m=models.MyModel

>>> obj=dbsession.query(m).get(1)
>>> obj
<testproj.models.mymodel.MyModel object at 0x0000020E9FD96DA0>
>>> obj.name
'one'

现在我们在 models 表中添加一行。首先声明一个 MyModel 类的对象,并将它添加到 dbsession 中。

Let us adda new row in the models table. First declare an object of the MyModel class, and add it in the dbsession.

>>> tm.begin()
>>> obj=models.MyModel(id=2, name='two', value=2)
>>> dbsession.add(obj)
>>> tm.commit()

Pyramid 使用在 pyramid_tm 包中声明的事务管理器对象 tm。要确认新记录已添加,请检索它。

Pyramid uses a transaction manger object tm which is declared in pyramid_tm package. To confirm that a new record is added, retrieve it back.

>>> obj=dbsession.query(models.MyModel).get(2)
>>> obj.name
'two'

这也可以通过在 SQLite GUI 工具中实际查看数据库的 models 表来确认。

This can also be confirmed by actually looking at the models table of the database in a SQLite GUI tool.

sqlite

prequest

prequest 实用工具可让你测试 URL 模式的响应,而无需实际启动服务器。命令需要配置文件和 URL 路径作为命令行参数。例如 −

The prequest utility lets you to test the response of a URL pattern without actually starting the server. The command needs the configuration file and the URL path as the command line arguments. For example −

Env>prequest development.ini /

该命令会生成我们之前看到的 Cookiecutter 主页的原始 HTML 响应。

The command produces the raw HTML response of the Cookiecutter homepage that we have seen earlier.

可以使用一些命令行开关。-d 选项显示服务器返回的状态和标题。要覆盖默认的 GET 请求方法,我们可以使用 -m 选项。

There are a couple of command line switches that can be used. The -d option displays the status and headers returned by the server. To override the default GET request method, we can use -m option.

proutes

此命令行 Pyramid 脚本显示所有已添加到你应用程序注册表中的路由。它只接受一个参数,即配置文件 (development.ini)

This command line Pyramid script displays all the routes added to your application’s registry. It accepts just one argument i.e. the configuration file (development.ini)

proutes 命令会显示 testproj 包的以下路由配置 −

Following route configuration of the testproj package is displayed by the proutes command −

Env>proutes development.ini
Name                       Pattern                                                        View
----                       -------                                                        ----
__static/                  /static/*subpath                                               testproj:static/
home                       /                                                              testproj.views.default.my_view
debugtoolbar               /_debug_toolbar/*subpath                                       <unknown>
__/_debug_toolbar/static/  /_debug_toolbar/static/*subpath pyramid_debugtoolbar:static/

Python Pyramid - Testing

编写测试脚本以确保代码正常工作被认为是一种良好的编程实践。Python 生态系统具有多个测试框架,包括包含在标准库中的 unittestPytest 是一个流行的测试库。它是 Pyramid 项目的首选库。

Writing test scripts which ensure that your code works correctly is considered as a good programming practice. Python ecosystem had a number of testing frameworks, including unittest which is bundled in the standard library. Pytest is a popular testing library. It is a preferred library for Pyramid projects.

我们将使用我们之前在展示 PasteDeploy 配置使用方法时开发的 hello 包。

We shall use the hello package that we developed earlier while demonstrating the use of PasteDeploy configuration.

首先,确保 Pyramid 环境安装了 PyTest 包。

First, ensure that the Pyramid environment has PyTest package installed.

pip3 install pytest

打开 hello 包中的 setup.py 文件并通过添加以粗体显示的行对其进行修改。

Open the setup.py file in hello package and modify it by adding the lines shown in bold.

from setuptools import setup

requires = [
   'pyramid',
   'waitress',
]
dev_requires = ['pytest',]
setup(
   name='hello',
   install_requires=requires,
   extras_require={
      'dev': dev_requires,
   },
   entry_points={
      'paste.app_factory': [
         'main = hello:main'
      ],
   },
)

此处,只要使用以下命令安装(或重新安装),就会将 Pytest 添加为项目依赖项 −

Here, Pytest is added as the project dependency whenever it is installed (or reinstalled) using following command −

pip3 install -e ".[dev]

将以下 Python 代码存储为 hello 包中的 testing.py。

Store the following Python code as testing.py in hello package.

import unittest
from pyramid import testing
class HelloTests(unittest.TestCase):
   def test_hello_world(self):
      from . import hello_world
      request = testing.DummyRequest()
      response = hello_world(request)
      self.assertEqual(response.status_code, 200)

要运行测试,请使用以下 Pytest 命令。测试的输出如下所示 −

To run the tests, use following Pytest command. The output of the test is shown below −

Env\hello>pytest tests.py
========================== test session starts ==========================
platform win32 -- Python 3.10.1, pytest-7.1.2, pluggy-1.0.0
rootdir: E:\tp-pyramid\hello
collected 1 item

tests.py.
   [100%]

=========================== 1 passed in 1.12s ===========================

要检查测试是否失败,请在测试函数中引发错误并重新运行。

To check if the test fails, induce an error in the test function and run again.

(tp-pyramid) E:\tp-pyramid\hello>pytest tests.py
========================== test session starts ==========================
collected 1 item

tests.py F
[100%]
=============================== FAILURES ================================
______________________ HelloTests.test_hello_world ______________________
self = <hello.tests.HelloTests testMethod=test_hello_world>
   def test_hello_world(self):
      from . import hello_world
      request = testing.DummyRequest()
      response = hello_world(request)
>     self.assertEqual(response.status_code, 404)
E     AssertionError: 200 != 404

tests.py:13: AssertionError
======================== short test summary info ========================
FAILED tests.py::HelloTests::test_hello_world - AssertionError: 200 != 404
=========================== 1 failed in 1.53s ===========================

Functional Testing

尽管单元测试在测试驱动开发 (TDD) 方法中广为使用,但对于 Web 应用程序, WebTest 是一个执行功能测试的 Python 包。我们可以对 WSGI 应用程序模拟完整的 HTTP 请求,然后测试响应中的信息。

Although Unit tests are popularly used in test-driven development (TDD)approach, for web applications, WebTest is a Python package that does functional testing. We can simulate a full HTTP request against a WSGI application, then test the information in the response.

Example

让我们使用我们在前面示例中使用的 hello 项目。打开 setup.py 并将 WebTest 添加为项目依赖项。

Let us use the hello project that we had used in the earlier example. Open the setup.py and add WebTest as the project dependency.

from setuptools import setup

requires = [
   'pyramid',
   'waitress',
]
dev_requires = ['pytest','webtest',]
setup(
   name='hello',
   install_requires=requires,
   extras_require={
      'dev': dev_requires,
   },
   entry_points={
      'paste.app_factory': [
         'main = hello:main'
      ],
   },
)

重新安装 hello 包及其新依赖项以进行开发模式。

Reinstall the hello package and its new dependency for development mode.

Env\hello>..\scripts\pip3 install -e ".[dev]"

tests.py 文件中包含一个功能测试

Include a functional test in tests.py file

import unittest
from pyramid import testing

class HelloTests(unittest.TestCase):

   def test_hello_world(self):
      from . import hello_world
      request = testing.DummyRequest()
      response = hello_world(request)
      self.assertEqual(response.status_code, 200)
class HelloFunctionalTests(unittest.TestCase):
   def setUp(self):
      from . import main
      app = main({})
      from webtest import TestApp
      self.testapp = TestApp(app)
   def test_hello_world(self):
      res = self.testapp.get('/', status=200)
      self.assertIn(b'<h1>Hello World!</h1>', res.body)

Output

最后,按照以下命令运行 Pytest −

Finally run Pytest as per the following command −

Env\hello>pytest tests.py
========================== test session starts ==========================
platform win32 -- Python 3.10.1, pytest-7.1.2, pluggy-1.0.0
rootdir: E:\tp-pyramid\hello
collected 2 items
tests.py .. [100%]

=========================== 2 passed in 2.37s ===========================

Tests in Cookiecutter Project

CookieCutter 实用程序自动生成包含功能测试和单元测试的测试包。我们之前曾使用 Cookiecutter 构建名为 testproj 的 Pyramid 项目。在此项目中,我们找到 tests 文件夹。

The CookieCutter utility auto-generates the tests package containing functional tests and unit tests. We had earlier used Cookiecutter to build Pyramid project named testproj. In this project, we find tests folder.

Example

test_functional py 包含以下测试函数 −

The test_functional py contains the following test functions −

from testproj import models

def test_my_view_success(testapp, dbsession):
   model = models.MyModel(name='one', value=55)
   dbsession.add(model)
   dbsession.flush()
   res = testapp.get('/', status=200)
   assert res.body

def test_notfound(testapp):
   res = testapp.get('/badurl', status=404)
   assert res.status_code == 404

test_views.py 定义了以下测试函数来测试视图 −

The test_views.py defines following test functions to test the views −

from testproj import models
from testproj.views.default import my_view
from testproj.views.notfound import notfound_view

def test_my_view_failure(app_request):
info = my_view(app_request)
assert info.status_int == 500

def test_my_view_success(app_request, dbsession):
   model = models.MyModel(name='one', value=55)
   dbsession.add(model)
   dbsession.flush()
   info = my_view(app_request)
   assert app_request.response.status_int == 200
   assert info['one'].name == 'one'
   assert info['project'] == 'testproj'
def test_notfound_view(app_request):
   info = notfound_view(app_request)
   assert app_request.response.status_int == 404
   assert info == {}

Output

这些测试由以下命令运行 −

These tests are run by the following command −

Env\testproj>Pytest
========================== test session starts ==========================
platform win32 -- Python 3.10.1, pytest-7.1.2, pluggy-1.0.0
rootdir: Env\testproj, configfile: pytest.ini, testpaths: testproj, tests
plugins: cov-3.0.0
collected 5 items

tests\test_functional.py .. [ 40%]
tests\test_views.py ... [100%]
=============== 5 passed, 20 warnings in 6.66s ===============

Python Pyramid - Logging

为了收集有关应用程序的有用信息,Pyramid 使用 Python 标准库中的 logging 模块。它在开发和生产模式中均可用于检测应用程序运行期间是否存在问题。应用程序日志可以包括您自己的消息和来自第三方模块的消息。

In order to collect useful information about the application, Pyramid uses the logging module from Python’s standard library. It proves useful in development as well as production mode to detect problems if any, during the running of the application. The application log can include your own messages integrated with messages from third-party modules.

已记录的消息具有以下预定义类型(按严重性递减排列)−

The logged messages have following predefined types (in the order of decreasing severity) −

  1. CRITICAL

  2. ERROR

  3. WARNING

  4. INFO

  5. DEBUG

  6. NOTSET

默认情况下,日志消息会被重定向到 sys.stderr 流。要开始收集日志消息,我们需要声明一个 Logger 对象。

By default, he logging messages are redirected to sys.stderr stream. To start collecting logging messages, we need to declare a Logger object.

import logging
log = logging.getLogger(__name__)

现在可以使用与所需日志级别相对应的 logger 方法生成日志消息。要生成一条消息,该消息可能有助于调试应用程序,请使用 log.debug() 消息和适当的消息字符串。

Log messages can now be generated with logger methods corresponding to the desired logging levels. To generate a message which can prove useful for debugging the application, use log.debug() message with appropriate message string.

基于 PasteDeploy 配置的 Pyramid 应用程序使得启用并入日志记录支持非常容易。PasteDeploy 文件(development.ini 和 production.ini)使用 ConfigParser 格式,该格式用于日志记录模块的配置参数中。当 pserve 命令调用 development.ini 中与日志记录相关的部分时,它们会传递到日志记录模块的配置流程中。

A Pyramid application based on PasteDeploy configuration makes it very easy to enable incorporate logging support. The PasteDEploy files (development.ini as well as production.ini) use the ConfigParser format used in the logging module’s configuration parameters. The logging related sections in development.ini are passed to the logging module’s configuration process when it is invoked by pserve command.

配置文件中的各种 logger 部分为应用程序对象指定了键、格式和日志记录级别。

Various logger sections in the configuration file specify the keys, formats and the logger levels for the application objects.

以下日志记录相关部分在典型的“development.ini”文件中声明 −

Following logging related sections are declared in a typical "development.ini" file −

# Begin logging configuration
[loggers]
keys = root, hello
[logger_hello]
level = DEBUG
handlers =
qualname = hello
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]

#level = INFO
level=DEBUG
handlers = console
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s %(levelname)-5.5s [%(name)s][%(threadName)s] %(message)s

# End logging configuration

让我们将这些部分添加到上一章中的 Hello 应用程序的 development.ini 文件中。

Let us add these sections in the development.ini file of our Hello application in the previous chapter.

Example

接下来,声明 Logger 对象并在 hello_world() few 函数中放置调试消息。以下是 init.py 代码 −

Next, declare the Logger object and put a debug message in the hello_world() few function. Here’s the init.py code −

from pyramid.config import Configurator
from pyramid.response import Response
from pyramid.view import view_config
import logging

log = logging.getLogger(__name__)

from pyramid.renderers import render_to_response

def hello_world(request):
   log.debug('In hello view')
   return render_to_response('templates/hello.html',
{'name':request.matchdict['name']},request=request)

def main(global_config, **settings):
   config = Configurator(settings=settings)
   config.include('pyramid_jinja2')
   config.add_jinja2_renderer(".html")
   config.add_route('hello', '/{name}')
   config.add_view(hello_world, route_name='hello')
   return config.make_wsgi_app()

hello_world() 视图会渲染以下 hello.html 模板-

The hello_world() view renders the following hello.html template −

<html>
   <body>
      <h1>Hello, {{ name }}!</h1>
   </body>
</html>

像往常一样运行该应用程序-

Run the application as usual −

pserve development.ini

http://localhost:6543/Tutorialpoint URL 输入到浏览器中时,命令窗口会回显以下调试消息-

When http://localhost:6543/Tutorialpoint URL is entered in the browser, the command window echoes following debug message −

Starting monitor for PID 11176.
Starting server in PID 8472.
2022-06-26 01:22:47,032 INFO [waitress][MainThread] Serving on http://[::1]:6543
2022-06-26 01:22:47,032 INFO [waitress][MainThread] Serving on http://127.0.0.1:6543
2022-06-26 01:22:47,418 DEBUG [hello][waitress-1] In hello view

Output

因为已在配置中启用了调试工具栏,所以它显示在浏览器中-

Since the debug toolbar is enabled in the configuration, it is displayed in the browser −

hello tp

调试消息还显示在调试工具栏的日志选项卡中,如下所示-

The debug message is also displayed on the logging tab of the debug toolbar as shown below −

log msgs

Python Pyramid - Security

Pyramid 的声明式安全系统确定当前用户的身份,并验证用户是否有权访问某些资源。安全策略可以防止用户调用视图。在调用任何视图之前,授权系统使用请求中的凭证来确定是否允许访问。

Pyramid’s declarative security system determines the identity of the current user and verifies if the user has access to certain resources. The security policy can prevent the user from invoking a view. Before any view is invoked, the authorization system uses the credentials in the request to determine if access will be allowed.

安全策略定义为一个类,该类借助 pyramid.security 模块中定义的以下方法控制用户访问:

The security policy is defined as a class that controls the user access with the help of following methods defined in pyramid.security module −

  1. forget(request) − This method returns header tuples suitable for 'forgetting' the set of credentials possessed by the currently authenticated user. It is generally used within the body of a view function.

  2. remember(request, userid) − This method returns a sequence of header tuples on the request’s response. They are suitable for 'remembering' a set of credentials such as userid using the current security policy. Common usage might look like so within the body of a view function.

此模块中 AllowedDenied 类对象控制经过验证用户的访问。

The authenticated user’s access is controlled by the objects of Allowed and Denied classes in this module.

要实现身份的功能,记住和忘记机制,Pyramid 提供了以下 helper 类,这些类定义在 pyramid.authentication 模块中:

To implement the functionality of identity, remember and forget mechanism, Pyramid provides the following helper classes defined in the pyramid.authentication module −

  1. SessionAuthenticationHelper − Store the userid in the session.

  2. AuthTktCookieHelper − Store the userid with an "auth ticket" cookie.

我们还可以使用 extract_http_basic_credentials() 函数使用 HTTP Basic Auth 检索用户凭证。

We can also use extract_http_basic_credentials() function to retrieve user credentials using HTTP Basic Auth.

要从 WSGI 环境中的 REMOTE_USER 中检索用户 ID,可以使用 request.environ.get('REMOTE_USER')

To retrieve the userid from REMOTE_USER in the WSGI environment, the request.environ.get('REMOTE_USER') can be used.

Example

下面让我们学习如何使用以下示例实现安全策略。此示例的“development.ini”如下:

Let us now learn how to implement the security policy with the help of following example. The "development.ini" for this example is as follows −

[app:main]
use = egg:tutorial
pyramid.reload_templates = true
pyramid.includes = pyramid_debugtoolbar
hello.secret = a12b

[server:main]
use = egg:waitress#main
listen = localhost:6543

然后我们在以下 Python 代码(另存为 security.py )中编写安全策略类:

We then write the security policy class in the following Python code saved as security.py

from pyramid.authentication import AuthTktCookieHelper
USERS = {'admin': 'admin', 'manager': 'manager'}
class SecurityPolicy:
   def __init__(self, secret):
      self.authtkt = AuthTktCookieHelper(secret=secret)
   def identity(self, request):
      identity = self.authtkt.identify(request)
      if identity is not None and identity['userid'] in USERS:
      return identity
   def authenticated_userid(self, request):
      identity = self.identity(request)
      if identity is not None:
         return identity['userid']
   def remember(self, request, userid, **kw):
      return self.authtkt.remember(request, userid, **kw)
   def forget(self, request, **kw):
      return self.authtkt.forget(request, **kw)

package 文件夹中的 init.py 文件定义以下配置。上面定义的安全策略类已通过 Configurator 类的 set_security_policy() 方法添加到配置中。三个路由 - home、login 和 logout - 已添加到配置中。

The init.py file in our package folder defines following configuration. The security policy class defined above is added in the configuration with set_security_policy() method of Configurator class. Three routes - home, login and logout – are added to the configuration.

from pyramid.config import Configurator
from .security import SecurityPolicy

def main(global_config, **settings):
   config = Configurator(settings=settings)
   config.include('pyramid_chameleon')
   config.set_security_policy(
      SecurityPolicy(
         secret=settings['hello.secret'],
      ),
   )
   config.add_route('home', '/')
   config.add_route('login', '/login')
   config.add_route('logout', '/logout')
   config.scan('.views')
   return config.make_wsgi_app()

与上述路由相对应的三个视图已在 views.py 中定义。

Three views corresponding to the above routes are defined in views.py.

from pyramid.httpexceptions import HTTPFound
from pyramid.security import remember, forget

from pyramid.view import view_config, view_defaults
from .security import USERS

@view_defaults(renderer='home.pt')
class HelloViews:
   def __init__(self, request):
      self.request = request
      self.logged_in = request.authenticated_userid
   @view_config(route_name='home')
   def home(self):
      return {'name': 'Welcome'}
   @view_config(route_name='login', renderer='login.pt')
   def login(self):
      request = self.request
      login_url = request.route_url('login')
      referrer = request.url
      if referrer == login_url:
         referrer = '/'
      came_from = request.params.get('came_from', referrer)
      message = ''
      login = ''
      password = ''
      if 'form.submitted' in request.params:
         login = request.params['login']
         password = request.params['password']
         pw = USERS.get(login)
         if pw == password:
            headers = remember(request, login)
            return HTTPFound(location=came_from, headers=headers)
         message = 'Failed login'
         return dict(
            name='Login', message=message,
            url=request.application_url + '/login',
            came_from=came_from,
            login=login, password=password,)

   @view_config(route_name='logout')
   def logout(self):
      request = self.request
      headers = forget(request)
      url = request.route_url('home')
      return HTTPFound(location=url, headers=headers)

登录视图呈现登录表单。当用户输入的用户 ID 和密码与 USERS 列表进行验证时,这些详细信息将被“记住”。另一方面,注销视图通过“忘记”释放这些详细信息。

The login view renders the login form. When the user Id and password entered by the user are verified against the list of USERS, the details are 'remembered'. On the other hand, the logout view releases these details by 'forgetting'.

home 视图呈现以下 chameleon 模板 - home.pt

The home view renders the following chameleon template - home.pt

<!DOCTYPE html>
<html lang="en">
<body>
   <div>
      <a tal:condition="view.logged_in is None" href="${request.application_url}/login">Log In</a>
      <a tal:condition="view.logged_in is not None" href="${request.application_url}/logout">Logout</a>
   </div>
   <h1>Hello. ${name}</h1>
</body>
</html>

以下是登录视图的 chameleon 模板 login.pt

Following is the chameleon template login.pt for login view.

<!DOCTYPE html>
<html lang="en">
<body>
   <h1>Login</h1>
   <span tal:replace="message"/>

   <form action="${url}" method="post">
      <input type="hidden" name="came_from" value="${came_from}"/>
      <label for="login">Username</label>
      <input type="text" id="login" name="login" value="${login}"/><br/>
      <label for="password">Password</label>
      <input type="password" id="password" name="password" value="${password}"/><br/>
      <input type="submit" name="form.submitted" value="Log In"/>
   </form>
</body>
</html>

development.ini 和 setup.py 位于外部项目文件夹中,而 init.py, views.py, security.py 和模板 home.pt 以及 login.pt 应保存在名为 hello 的包文件夹中。

The development.ini and setup.py are placed in the outer project folder, while, the init.py, views.py, security.py and the templates home.pt as well as login.pt should be saved under the package folder named hello.

使用以下命令安装该软件包 −

Install the package with the following command −

Env\hello>pip3 install -e.

使用 pserve 实用工具启动服务器。

Start the server with the pserve utility.

pserve development.ini

Output

打开浏览器并访问 http://localhost:6543/ 链接。

Open the browser and visit http://localhost:6543/ link.

welcome

单击“登录”链接以打开登录表单 −

Click the "Log In" link to open the login form −

login

home 视图页面返回,链接改为注销,因为这些凭据已被记住。

The home view page comes back with the link changed to logout as the credentials are remembered.

hello

单击“注销”链接将导致忘记凭据,并将显示默认主页。

Clicking the "logout" link will result in forgetting the credentials and the default home page will be shown.

Python Pyramid - Deployment

本教程中到目前为止开发的 Pyramid 应用程序示例已在本地机器上执行。要使其公开访问,必须将其部署在能够满足 WSGI 标准的生产服务器上。

The examples of Pyramid applications developed so far in this tutorial have been executed on the local machine. To make it accessible publicly, it must be deployed on a Production server capable of WSGI standards.

为此提供了许多与 WSGI 兼容的 http 服务器。例如 -

Many WSGI compatible http servers are available for this purpose. For example −

  1. waitress

  2. paste.httpserver

  3. CherryPy

  4. uWSGI

  5. gevent

  6. mod_wsgi

我们已经讨论了如何使用 Waitress 服务器来托管 Pyramid 应用程序。它可以在具有公有 IP 地址的机器的 80 端口(HTTP)和 443 端口(HTTPS)上提供服务。

We have discussed how we can use Waitress server to host a Pyramid application. It can be served on ports 80 (HTTP) and 443 (HTTPS) of a machine having a public IP address.

mod_wsgi

Apache 服务器是 Apache Software Foundation 分发的流行开源 HTTP 服务器软件。它为互联网上的大多数 Web 服务器提供支持。 mod_wsgi (由 Graham Dumpleton 开发)是 Apache 模块,它为在 Apache 上部署基于 Python 的 Web 应用程序提供了 WSGI 接口。

Apache server is a popular open source HTTP server software, distributed by Apache Software Foundation. It powers most of the web servers across internet. The mod_wsgi (developed by Graham Dumpleton) is an Apache module that provides a WSGI interface for deploying Python based web applications on Apache.

本节解释了在 Apache 服务器上部署 Pyramid 应用程序的分步过程。在此,我们将使用 XAMPP,这是一个流行的开源 Apache 发行版。它可以从 https://www.apachefriends.org/download.html. 下载

In this section, the step by step procedure to deploy a Pyramid application on the Apache server is explained. Here, we’ll use XAMPP, a popular open source Apache distribution. It can be downloaded from https://www.apachefriends.org/download.html.

mod_wsgi 模块使用 PIP 安装程序安装。在安装之前,将 MOD_WSGI_APACHE_ROOTDIR 环境变量设置为 Apache 可执行文件所在的目录。

The mod_wsgi module is installed with PIP installer. Before installing, set the MOD_WSGI_APACHE_ROOTDIR environment variable to the directory in which Apache executable is located.

C:\Python310\Scripts>set MOD_WSGI_APACHE_ROOTDIR=C:/xampp/apache
C:\Python310\Scripts>pip install mod_wsgi

接下来,在命令终端中运行以下命令。

Next, run the following command in the command terminal.

C:\Python310\Scripts>mod_wsgi-express module-config
LoadFile "C:/Python310/python310.dll"
LoadModule wsgi_module "C:/Python310/lib/site-packages/mod_wsgi/server/mod_wsgi.cp310-win_amd64.pyd"
WSGIPythonHome "C:/Python310"

这些是 mod_wsgi 模块设置,需纳入 Apache 的配置文件。打开 XAMPP 安装的 httpd.conf 文件,然后在其中复制上述命令行的输出。

These are mod_wsgi module settings to be incorporated Apache’s configuration file. Open httpd.conf file of your XAMPP installation and copy the output of the above command line in it.

接下来,为我们的应用程序创建一个虚拟主机配置。Apache 将虚拟主机信息存储在 httpd-vhosts.conf 文件中,该文件位于 C:\XAMPP\Apache\conf\extra\ 文件夹中。打开该文件,并在其中添加以下行 −

Next, create a virtual host configuration for our application. Apache stores virtual host information in httpd-vhosts.conf file which is found in C:\XAMPP\Apache\conf\extra\ folder. Open the file and add following lines in it −

<VirtualHost *>
   ServerName localhost:6543
   WSGIScriptAlias / e:/pyramid-env/hello/production.ini
   <Directory e:/pyramid-env/hello>
      Order deny,allow
      Allow from all
      Require all granted
   </Directory>
</VirtualHost>

此处假定使用 Cookiecutter 实用程序构建了 hello Pyramid 项目。此处使用要在生产环境中使用的 PasteDeploy 配置文件。

Here, it is assumed that a hello Pyramid project is built using the Cookiecutter utility. The PasteDeploy configuration file to be used in production environment is used here.

此虚拟主机配置需要纳入 Apache 的 httpd.conf 文件。方法是在其中添加以下行 −

This virtual host configuration needs to be incorporated in Apache’s httpd.conf file. This is done by adding following lines in it −

# Virtual hosts
   Include conf/extra/httpd-vhosts.conf

现在,我们必须将以下代码另存为 pyramid.wsgi 文件,该文件返回 Pyramid WSGI 应用程序对象。

We now have to save the following code as pyramid.wsgi file that returns the Pyramid WSGI application object.

from pyramid.paster import get_app, setup_logging
ini_path = 'e:/pyramid-env/hello/production.ini'
setup_logging(ini_path)
application = get_app(ini_path, 'main')

在执行上述过程后,重新启动 XAMPP 服务器,我们应该能够在 Apache 服务器上运行 Pyramid 应用程序。

After performing the above mentioned procedure, restart the XAMPP server and we should be able to run the Pyramid application on the Apache server.

Deploy on Uvicorn

Uvicorn 是 ASGI 兼容的服务器(ASGI 表示异步网关接口)。由于 Pyramid 是基于 WSGI 的 Web 框架,因此我们需要使用 asgiref.wsgi 模块中定义的 WsgiToAsgi() 函数将 WSGI 应用程序对象转换为 ASGI 对象。

Uvicorn is an ASGI compatible server (ASGI stands for Asynchronous Gateway Interface). Since Pyramid is a WSGI based web framework, we need to convert the WSGI application object to ASGI object, with the help of WsgiToAsgi() function defined in asgiref.wsgi module.

from asgiref.wsgi import WsgiToAsgi
from pyramid.config import Configurator
from pyramid.response import Response

def hello_world(request):
   return Response("Hello")

with Configurator() as config:
   config.add_route("hello", "/")
   config.add_view(hello_world, route_name="hello")
   wsgi_app = config.make_wsgi_app()

app = WsgiToAsgi(wsgi_app)

将以上代码另存为 app.py。使用 pip 实用程序安装 Uvicorn。

Save the above code as app.py. Install Uvicorn with pip utility.

pip3 install uvicorn

在 ASGI 模式下运行 Pyramid 应用程序。

Run the Pyramid application in ASGI mode.

uvicorn app:app

类似地,它可以使用 daphne 服务器提供服务。

Similarly, it can be served using daphne server.

daphne app:app