Python Pyramid 简明教程

Python Pyramid - Quick Guide

Python Pyramid - Overview

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

  1. Pyramid 是一个极简主义的 Web 框架。它不会打包任何模板库,也没有对任何特定数据库包的支持。

  2. 但是,可以通过 SQLAlchemy 将其与 SQL 数据库集成,也能与 Zope Object Database 以及 CouchDB 等其他 NoSQL 数据库相集成。

  3. Pyramid 还可以配置为可与诸如 Mako、Jinja2 或 Chameleon 的模板库配合使用。

  4. Pyramid 是由 Chris McDonough 开发的。Pyramid 的第一个版本于 2011 年 1 月发布。最新版本 Pyramid 2.0 于 2021 年 3 月发布。

Comparison with Other Python Frameworks

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

  1. Pyramid 在很大程度上基于 repose.bfg 框架。它与 Pylons 项目合并后,它于 2010 年更名为 Pyramid。

  2. 扩展 Pyramid 应用的功能是从 Zope 库借来的。无需修改应用代码,就可以重复使用、修改或扩展应用。声明性安全层和路由遍历等功能是从 Zope 继承的。

  3. 就像 Pylons 1.0 的情况一样,Pyramid 也不会强制实施任何策略。它还允许用户选择任何数据库或模板系统,URL 调度方法也受到 Pylons 的启发。

  4. views 的概念基于 Django 的类似方法。广泛的文档也是 Pyramid 采用的 Django 功能。

  5. 尽管定义并不完全符合,但可以说 Pyramid 遵循 MVC(模型-视图-控制器)方法。

Python Pyramid - Environment Setup

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

pip3 install pyramid

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

pip3 install waitress

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

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

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() 方法时,服务器对象会进入侦听循环。

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

Python hello.py

Output

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

hello world

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

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”消息。

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

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 类用于构建应用程序注册表。

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

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

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

add_route()

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

  1. name - 第一个必需的位置参数必须是路由的唯一名称。当注册视图或生成 URL 时,名称用于标识路由。

  2. pattern - 第二个必需的位置参数是一个字符串,表示 URL 路径(可选地包含变量占位符,用于从 URL 解析变量数据)。占位符用花括号括起来。例如:"/students/{id}"。

  3. request_method - 值可以是“GET”、“POST”、“HEAD”、“DELETE”、“PUT”之一。只有此类型的请求将与路由匹配。

add_view()

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

  1. view - 视图函数的名称。

  2. route_name - 必须与路由配置声明的名称匹配的字符串。

  3. request_method - 表示 HTTP REQUEST_METHOD 的字符串(如“GET”、“POST”、“PUT”、“DELETE”、“HEAD”或“OPTIONS”)或包含一个或多个此类字符串的元组。

add_static_view()

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

  1. name - 此参数是一个字符串,表示应用程序相对的本地 URL 前缀或一个完整的 URL。

  2. Path - 此参数表示静态文件所在磁盘上的路径。其值可以是绝对路径或包相对路径。

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

add_notfound_view()

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

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().

add_exception_view()

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

make_wsgi_app()

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

scan()

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

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

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

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 提供了另一种配置方法,称为装饰配置。

Declarative Configuration

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

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

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

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() 函数添加了一个属性,使其可供扫描在之后找到它。

Example

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

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

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() 方法的调用,因此,该操作等同于以下语句:

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

Output

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

config

Python Pyramid - Url Routing

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

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

mysite

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

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

Route Configuration

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

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

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

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

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

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

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

Example

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

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 路由匹配,因此显示以下输出:

root configuration

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

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

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

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

Route Matching

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

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

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

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

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

parameters

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

Example

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

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

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 时,输出如下

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

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

Title: Python, Price: 300

Python Pyramid - View Configuration

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

有三种配置 view 的方式 −

  1. Using add_view() method

  2. Using @view_config() decorator

  3. 使用 @view_defaults 类装饰器

Using add_view() Method

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

此方法使用以下参数 −

  1. name − 与此视图可调用对象匹配所需的视图名称。如果未提供名称,则使用空字符串(表示默认视图)。

  2. context − 此资源必须是 Python 类的对象,以便找到和调用此视图。如果未提供上下文,则使用值 None,该值与任何资源匹配。

  3. route_name − 此值必须与在调用此视图之前必须匹配的路由配置声明的名称匹配。如果提供了 route_name,则仅当已匹配已命名路由时才调用视图可调用对象。

  4. request_type − 请求必须提供的接口才能找到并调用此视图。

  5. request_method − 一个字符串(如“GET”、“POST”、“PUT”、“DELETE”、“HEAD”或“OPTIONS”),表示 HTTP REQUEST_METHOD 或者包含这些字符串中一个或多个的元组。只有当请求的 method 属性与提供的某个值匹配时,才会调用此视图。

  6. request_param − 此参数可以是任何字符串或字符串序列。只有当 request.params 字典具有与提供的某个值匹配的键时,才会调用此视图。

Example

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

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

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 命令行实用程序。

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 方法。

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

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

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

Output

CURL 输出如下所示:

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 装饰器将配置的路由与函数、方法甚至可调用的类关联起来。

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() 方法后,才会将视图添加到应用程序配置中。虽然消除了命令式添加视图的需要,但性能可能会略微下降。

Output

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

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

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

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

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

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() 方法来添加视图,而不扫描视图配置。

Example

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

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 命令的输出:

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() 执行每个方法的配置。

Example

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

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 命令如下所示−

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()

#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() 函数时,最终将注册这些路由。

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

#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 参数来完成的。

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

因此,语句

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

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

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

Example

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

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 命令来测试这些路由。

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 标签,如下例所示 −

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/ ,浏览器渲染以下输出 −

templates

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

Template Bindings

Pyramid 提供了使用诸如 jinja2、Mako 和 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

pip3 install pyramid_jinja2

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

config.include('pyramid_jinja2')

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

render_to_response(renderer_name, value, request)

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

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

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

Jinja2 Template Library

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

Expressions

  1. {{ …​ }} 用作要打印到模板输出的表达式。

  2. {% …​ %} 用作语句。

  3. {# …​ #} 用作不包含在模板输出中的注释。

Conditionals

  1. {% if expr %}

  2. {% else %}

  3. {% endif %}

Loop

  1. {% for var in iterable %}

  2. {% endfor %}

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

Rendering Template

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

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。完整的应用程序代码如下 −

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/ 。浏览器显示以下结果 −

hellotp

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

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

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

from pyramid.renderers import render

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

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

Rendering via Configuration

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

  1. JSON

  2. String

  3. JSONP

Example

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

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 响应,如下图所示 −

json

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

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

hello.jinja2 模板 HTML 代码如下 −

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

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

from pyramid.view import view_config

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

Example

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

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

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 函数提供可变数据的模板网页如下所示 −

view

Add/Change Renderer

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

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

config.add_jinja2_renderer(".html")

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

from pyramid.view import view_config

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

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

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 对象传递,而该对象反过来可以作为上下文数据传递给要渲染的模板。

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

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

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

from pyramid.view import view_config

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

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='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 模板使用,并渲染出以下输出。

jinja2

Conditionals and Loops in Template

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

Conditionals

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

Loop

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

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

Example

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

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 语句语法显示通过/失败结果。

<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/ 链接渲染出以下表格结果 −

marklist

Python Pyramid - HTML Form Template

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

<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 表单 −

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

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

@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 模板。

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

<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 表单、解析表单数据和生成显示学生成绩表表格的页面的完整代码,其中包含视图 −

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/ 以获取如下所示的表单 −

student

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

students url

Python Pyramid - Static Assets

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

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

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

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

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

Static Image

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

<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() 视图呈现上述模板。

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 徽标。

pyramid

Javascript as Static Asset

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

<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 代码如下:

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 变量分配适当的值(早上好、下午好或晚上好)。

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

goodevening

Python Pyramid - Request Object

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

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

以下是一些 attributes

  1. request.method — 客户端用于发送数据的 HTTP 请求方法,例如 GET、POST

  2. request.GET — 此属性是包含查询字符串中所有变量的多词典。

  3. request.POST — 此属性仅在请求为 POST 且它为表单提交时可用。它是包含请求主体中所有变量的多词典。

  4. request.params − 按 request.GET 和 request.POST 中的所有内容组合的多词典。

  5. request.body − 此属性包含整个请求体,格式为字符串。在请求为非表单提交的 POST 或者 PUT 等请求时,这很有用。

  6. request.cookies − 包含所有 Cookie。

  7. request.headers − 不区分大小写的所有 HTTP 头的词典。

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

  1. request.url − 返回具有查询字符串的完整请求 URL,例如 [role="bare"] [role="bare"]http://localhost:6543/app?name=Ravi

  2. request.host − URL 中的主机信息,例如 localhost

  3. request.host_url − 此属性返回带有主机的 URL,例如 [role="bare"] [role="bare"]http://localhost:6543/

  4. request.application_url − 应用程序的 URL(不含 PATH_INFO),例如 [role="bare"] [role="bare"]http://localhost:6543/app

  5. request.path_url − 包含应用程序的 URL,包括 PATH_INFO,例如 [role="bare"] [role="bare"]http://localhost:66543/app

  6. request.path − 返回包括 PATH_INFO 的 URL,不含主机,例如 "/app"

  7. request.path_qs − URL 中的查询字符串,包括 PATH_INFO,例如 "/app?name=Ravi"

  8. request.query_string − URL 中仅包含查询字符串,例如 "name=Ravi"

Python Pyramid - Response Object

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

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

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

  1. response.content_type − 内容类型是一个字符串,例如 – response.content_type = 'text/html'

  2. response.charset − 它还在 response.text 中提供编码信息。

  3. response.set_cookie − 此属性用于设置 Cookie。需要提供 name、value 和 max_age 这些参数。

  4. response.delete_cookie − 从客户端删除 Cookie。实际上就是将 max_age 设置为 0,并将 Cookie 值设置为 ''。

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

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 属性将客户端重定向到另一个路由。例如 −

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 中,它可用作请求对象的属性。

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

Default Session Factory

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

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

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

config.set_session_factory(my_session_factory)

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

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

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

user=request.session['user']

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

request.session.pop('user')

Session Example

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

@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() 函数读取“用户”表单属性并使用其值添加会话变量。

@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() 视图读回会话变量数据并显示欢迎消息。

@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)

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

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

以下是完整代码 −

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} 文件,才能将其视为包。

Output

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

submit

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

session

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

welcome admin

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

Python Pyramid - Events

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

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

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

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

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

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

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

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

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

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

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

  1. ApplicationCreated − 当调用 Configurator 类的 config.make_wsgi_app() 方法以返回 WSGI 应用程序对象时,才传播这个事件。

  2. NewRequest − 每当 Pyramid 应用程序开始处理一个传入请求时,就会发出这个事件类的对象。这个对象有一个 request 属性,它是由 WSGI 环境词典提供的请求对象。

  3. ContextFound − 应用程序的路由器遍历所有路由并找到与 URL 模式的匹配项。此时将实例化 ContextFound 类的对象。

  4. BeforeTraversal − 当 Pyramid 路由器尝试找到一个路由对象,但在执行任何遍历或视图代码之前,将发出此类的实例作为事件发送。

  5. NewResponse − 正如名称表明的那样,每当任何 Pyramid 视图可调用返回一个响应时,就会引发这个事件。这个对象具有 request 和 response 属性。

  6. BeforeRender − 当在调用呈现器之前,将将此类型的对象作为事件转发。对这个事件的订阅者函数可以访问应用程序的全局数据(它采用 dict 对象的形式),并且可以修改一个或多个键的值。

Python Pyramid - Message Flashing

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

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

request.session.flash('Hello World')

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

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

Message Flashing Example

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

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

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

@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 模板具有以下代码 −

<!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'> 部分中弹出每条消息。

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

<!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

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 文件夹中。

Output

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

pyramid message

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

weak password

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

loggedin

Python Pyramid - Using SQLAlchemy

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

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

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

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

pip install sqlalchemy

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

以下是包含的方言:

  1. Firebird

  2. Microsoft SQL Server

  3. MySQL

  4. Oracle

  5. PostgreSQL

  6. SQLite

  7. Sybase

Database Engine

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

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} 定义会话类 - 一个可配置的会话工厂方法,绑定到引擎对象。

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

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

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

Model Class

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

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. )进行确认

sqlitestudio

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

Add a New Student Record

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

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() 视图函数来渲染上述表单。

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" 模式一起注册,如下所示:

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() 方法来完成操作。

@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 模式。

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

Output

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

student details

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

student database

Show List of All Records

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

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

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

@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 脚本如下:

<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 注册它。

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

Output

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

add new

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

Update Existing Record

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

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

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

@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 代码如下:

<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

percentage

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

@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() 函数并显示更新的分数表。

updated marklist

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

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

Delete a Record

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

@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 呈现:

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

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

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

Output

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

record

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

  1. 在 Pyramid 虚拟环境中创建一个名为 testapp 的文件夹

  2. testapp 中创建 templates 文件夹。

  3. 在 testapp 中创建一个空 init.py ,使其成为包。

  4. 将 marklist.html、myform.html、showform.html 和 deleted.html 文件放入 “testapp\templates” 文件夹。上述的文件代码已在此提供。

  5. 将以下代码另存为 models.py ,并将其保存在 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. 将以下代码另存为 views.py ,并将其保存在 testapp 文件夹中。

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. 将以下代码另存为 main.py,并将其保存在 testapp 文件夹中。

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. 从命令提示符运行 main.py

Python main.py
  1. 在浏览器窗口中使用 http://localhost:6543/ URL。将显示一张仅有标题且没有记录的表格。

  2. 按照表格下方的添加新链接来添加记录。

  3. 单击表格中的“编辑”链接更新记录。

  4. 单击表格中的“删除”链接删除所选记录。

Python Pyramid - Cookiecutter

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

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

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

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

pip install cookiecutter

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

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

Python Pyramid - Creating A Project

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

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

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

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

接下来,选择模板语言。

选择 template_language

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

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

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

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

│ 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 子文件夹是一个具有模型、脚本、子包以及静态和模板文件夹的包。

接下来,使用 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 数据库。

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

initialize_testproj_db development.ini

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

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 上通过以下命令提供服务 −

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/ 。新创建的项目的主页将显示如下 −

cookiecutter

Debug Toolbar

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

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

pyramid  logo

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

debug toolbar

Python Pyramid - Project Structure

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

  1. MANIFEST.in 包含要包含在包源分发中的文件列表。

  2. development.ini 是 PasteDeploy 配置文件,可用用于在开发期间执行应用程序。

  3. production.ini 是 PasteDeploy 配置文件,可用用于在生产配置中执行应用程序。

  4. pytest.ini 是用于运行测试的配置文件。

  5. setup.py 是用于测试和分发应用程序的标准 Setuptools setup.py 文件。

  6. testing.ini 是用于执行应用程序的测试的配置文件。

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

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

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

development.ini

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

pserve development.ini

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

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

[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 项目文件夹中)中声明。此部分包含其它启动时间配置设置。

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

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

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

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

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

production.ini

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

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

[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 实用工具会自动在同名的父项目文件夹内创建一个包文件夹。包文件夹包括以下文件和子文件夹。

init.py

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

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

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 模式的主页路由。

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

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

Views Package

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

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。

Templates Folder

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

{% 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 模型的定义。

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 类对象。

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

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

setup.py

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

md hello
cd hello

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

from setuptools import setup

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

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

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

pip3 install -e.

development.ini

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

[app:main]
use = egg:hello

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

init.py

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

该代码是一个基本的 Hello World Pyramid 应用程序代码,具有 hello_world() 视图。 main() 函数将此视图注册到具有 '/' URL 模式的 hello 路由,并返回由 make_wsgi_app() 给出的 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 命令提供该应用程序。

pserve development.ini --reload

Python Pyramid - Command Line Pyramid

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

这些命令行脚本是-

  1. pserve − 提供一个使用 PasteDeploy 配置文件 Web 应用程序。

  2. pviews − 为给定 URL 显示匹配的视图。

  3. pshell − 交互式 Shell。

  4. proutes − 显示所有应用程序路由。

  5. ptweens − Displaying "Tweens".

  6. prequest − 调用请求。

  7. pdistreport − 显示所有已安装的发行版及其版本。

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

pserve

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

假设 Pyramid 项目 (testproj) 在 Pyramid 虚拟环境中创建在同名的文件夹中,以下命令开始侦听传入的浏览器请求-

Env>..\scripts\pserve development.ini

pserve 模块(以及其他 Pyramid 命令行脚本)可以用作命令提示符中 Python 解释器的参数运行。

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 实用程序更灵活,可以使用以下命令行参数-

  1. config_uri − 配置文件的 URI。

  2. -n &lt;name&gt; − 加载命名应用程序(默认的 main)。

  3. -s &lt;server_type&gt; − 使用命名的服务器。

  4. --server-name &lt;section_name&gt; − 使用在配置文件中定义的已命名服务器(默认值:main)

  5. --reload − 使用自动重启文件监视器。

  6. -b − 打开一个网络浏览器到服务器 url。

应用程序提供于 http://localhost:6543 的情况下,访问将受到限制,以致于只有运行在同一机器上的浏览器才能访问。如果您允许网络上其他机器访问,请编辑“development.ini”文件,然后像下面所示替换 [server:main] 部分中的侦听值 −

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

设置 *:6543 等于 0.0.0.0:6543 [::]:6543,结果,应用程序响应系统拥有的所有 IP 地址上的请求,而不仅仅是响应 localhost 的请求。

pserve 命令行中的 --reload 选项会在运行代码修改时自动重新加载应用程序。

使用 --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 文件进行了任何更改,服务器会自动重启 −

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。

让我们在先前使用 Cookiecutter 构建的 testproj 项目中使用 development.ini 文件执行 pviews 命令。

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,其下方显示所有匹配的视图及其视图配置详细信息。此示例中只有一个视图匹配,因此只有一个视图部分。

pshell

pshell 脚本使得可以用 Python 提示符与金字塔应用程序的环境进行交互。此 shell 使用 PasteDeploy 配置文件(如 development.ini)作为命令行参数(类似于其他金字塔脚本),并打开 Python 交互式 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 提示符中的其行为。

>>> root
<pyramid.traversal.DefaultRootFactory object at 0x0000020E9E2507F0>
>>> registry
<Registry testproj>

将注册表设置从“development.ini”读入词典。我们可以使用 for 循环遍历其内容 −

>>> 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 模块与数据库进行交互。

当我们首次完成 cookiecutter 步骤时,应用程序数据库最初被初始化。我们在“testproj.sqlite”数据库中发现一个 models 表格,其中有一个记录。

testproj

现在我们按照以下内容从 Python 提示符访问此表格 −

>>> m=models.MyModel

>>> obj=dbsession.query(m).get(1)
>>> obj
<testproj.models.mymodel.MyModel object at 0x0000020E9FD96DA0>
>>> obj.name
'one'

现在我们在 models 表中添加一行。首先声明一个 MyModel 类的对象,并将它添加到 dbsession 中。

>>> tm.begin()
>>> obj=models.MyModel(id=2, name='two', value=2)
>>> dbsession.add(obj)
>>> tm.commit()

Pyramid 使用在 pyramid_tm 包中声明的事务管理器对象 tm。要确认新记录已添加,请检索它。

>>> obj=dbsession.query(models.MyModel).get(2)
>>> obj.name
'two'

这也可以通过在 SQLite GUI 工具中实际查看数据库的 models 表来确认。

sqlite

prequest

prequest 实用工具可让你测试 URL 模式的响应,而无需实际启动服务器。命令需要配置文件和 URL 路径作为命令行参数。例如 −

Env>prequest development.ini /

该命令会生成我们之前看到的 Cookiecutter 主页的原始 HTML 响应。

可以使用一些命令行开关。-d 选项显示服务器返回的状态和标题。要覆盖默认的 GET 请求方法,我们可以使用 -m 选项。

proutes

此命令行 Pyramid 脚本显示所有已添加到你应用程序注册表中的路由。它只接受一个参数,即配置文件 (development.ini)

proutes 命令会显示 testproj 包的以下路由配置 −

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 项目的首选库。

我们将使用我们之前在展示 PasteDeploy 配置使用方法时开发的 hello 包。

首先,确保 Pyramid 环境安装了 PyTest 包。

pip3 install pytest

打开 hello 包中的 setup.py 文件并通过添加以粗体显示的行对其进行修改。

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 添加为项目依赖项 −

pip3 install -e ".[dev]

将以下 Python 代码存储为 hello 包中的 testing.py。

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 命令。测试的输出如下所示 −

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 ===========================

要检查测试是否失败,请在测试函数中引发错误并重新运行。

(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 请求,然后测试响应中的信息。

Example

让我们使用我们在前面示例中使用的 hello 项目。打开 setup.py 并将 WebTest 添加为项目依赖项。

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 包及其新依赖项以进行开发模式。

Env\hello>..\scripts\pip3 install -e ".[dev]"

tests.py 文件中包含一个功能测试

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 −

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 文件夹。

Example

test_functional py 包含以下测试函数 −

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 定义了以下测试函数来测试视图 −

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

这些测试由以下命令运行 −

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 模块。它在开发和生产模式中均可用于检测应用程序运行期间是否存在问题。应用程序日志可以包括您自己的消息和来自第三方模块的消息。

已记录的消息具有以下预定义类型(按严重性递减排列)−

  1. CRITICAL

  2. ERROR

  3. WARNING

  4. INFO

  5. DEBUG

  6. NOTSET

默认情况下,日志消息会被重定向到 sys.stderr 流。要开始收集日志消息,我们需要声明一个 Logger 对象。

import logging
log = logging.getLogger(__name__)

现在可以使用与所需日志级别相对应的 logger 方法生成日志消息。要生成一条消息,该消息可能有助于调试应用程序,请使用 log.debug() 消息和适当的消息字符串。

基于 PasteDeploy 配置的 Pyramid 应用程序使得启用并入日志记录支持非常容易。PasteDeploy 文件(development.ini 和 production.ini)使用 ConfigParser 格式,该格式用于日志记录模块的配置参数中。当 pserve 命令调用 development.ini 中与日志记录相关的部分时,它们会传递到日志记录模块的配置流程中。

配置文件中的各种 logger 部分为应用程序对象指定了键、格式和日志记录级别。

以下日志记录相关部分在典型的“development.ini”文件中声明 −

# 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 文件中。

Example

接下来,声明 Logger 对象并在 hello_world() few 函数中放置调试消息。以下是 init.py 代码 −

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 模板-

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

像往常一样运行该应用程序-

pserve development.ini

http://localhost:6543/Tutorialpoint URL 输入到浏览器中时,命令窗口会回显以下调试消息-

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

因为已在配置中启用了调试工具栏,所以它显示在浏览器中-

hello tp

调试消息还显示在调试工具栏的日志选项卡中,如下所示-

log msgs

Python Pyramid - Security

Pyramid 的声明式安全系统确定当前用户的身份,并验证用户是否有权访问某些资源。安全策略可以防止用户调用视图。在调用任何视图之前,授权系统使用请求中的凭证来确定是否允许访问。

安全策略定义为一个类,该类借助 pyramid.security 模块中定义的以下方法控制用户访问:

  1. forget(request) :此方法返回标头元组,这些元组适用于“忘记”当前经过验证的用户所拥有的凭证集。通常在视图函数的主体中使用。

  2. remember(request, userid) :此方法在请求的响应中返回标头元组序列。它们适合于“记住”一组凭证,例如使用当前安全策略的用户 ID。常见用法可能在视图函数的主体中看起来像这样。

此模块中 AllowedDenied 类对象控制经过验证用户的访问。

要实现身份的功能,记住和忘记机制,Pyramid 提供了以下 helper 类,这些类定义在 pyramid.authentication 模块中:

  1. SessionAuthenticationHelper :将用户 ID 存储在会话中。

  2. AuthTktCookieHelper :使用“auth ticket”cookie 将用户 ID 存储在会话中。

我们还可以使用 extract_http_basic_credentials() 函数使用 HTTP Basic Auth 检索用户凭证。

要从 WSGI 环境中的 REMOTE_USER 中检索用户 ID,可以使用 request.environ.get('REMOTE_USER')

Example

下面让我们学习如何使用以下示例实现安全策略。此示例的“development.ini”如下:

[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 )中编写安全策略类:

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 - 已添加到配置中。

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 中定义。

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 列表进行验证时,这些详细信息将被“记住”。另一方面,注销视图通过“忘记”释放这些详细信息。

home 视图呈现以下 chameleon 模板 - 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

<!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 的包文件夹中。

使用以下命令安装该软件包 −

Env\hello>pip3 install -e.

使用 pserve 实用工具启动服务器。

pserve development.ini

Output

打开浏览器并访问 http://localhost:6543/ 链接。

welcome

单击“登录”链接以打开登录表单 −

login

home 视图页面返回,链接改为注销,因为这些凭据已被记住。

hello

单击“注销”链接将导致忘记凭据,并将显示默认主页。

Python Pyramid - Deployment

本教程中到目前为止开发的 Pyramid 应用程序示例已在本地机器上执行。要使其公开访问,必须将其部署在能够满足 WSGI 标准的生产服务器上。

为此提供了许多与 WSGI 兼容的 http 服务器。例如 -

  1. waitress

  2. paste.httpserver

  3. CherryPy

  4. uWSGI

  5. gevent

  6. mod_wsgi

我们已经讨论了如何使用 Waitress 服务器来托管 Pyramid 应用程序。它可以在具有公有 IP 地址的机器的 80 端口(HTTP)和 443 端口(HTTPS)上提供服务。

mod_wsgi

Apache 服务器是 Apache Software Foundation 分发的流行开源 HTTP 服务器软件。它为互联网上的大多数 Web 服务器提供支持。 mod_wsgi (由 Graham Dumpleton 开发)是 Apache 模块,它为在 Apache 上部署基于 Python 的 Web 应用程序提供了 WSGI 接口。

本节解释了在 Apache 服务器上部署 Pyramid 应用程序的分步过程。在此,我们将使用 XAMPP,这是一个流行的开源 Apache 发行版。它可以从 https://www.apachefriends.org/download.html. 下载

mod_wsgi 模块使用 PIP 安装程序安装。在安装之前,将 MOD_WSGI_APACHE_ROOTDIR 环境变量设置为 Apache 可执行文件所在的目录。

C:\Python310\Scripts>set MOD_WSGI_APACHE_ROOTDIR=C:/xampp/apache
C:\Python310\Scripts>pip install mod_wsgi

接下来,在命令终端中运行以下命令。

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 文件,然后在其中复制上述命令行的输出。

接下来,为我们的应用程序创建一个虚拟主机配置。Apache 将虚拟主机信息存储在 httpd-vhosts.conf 文件中,该文件位于 C:\XAMPP\Apache\conf\extra\ 文件夹中。打开该文件,并在其中添加以下行 −

<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 配置文件。

此虚拟主机配置需要纳入 Apache 的 httpd.conf 文件。方法是在其中添加以下行 −

# Virtual hosts
   Include conf/extra/httpd-vhosts.conf

现在,我们必须将以下代码另存为 pyramid.wsgi 文件,该文件返回 Pyramid WSGI 应用程序对象。

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 应用程序。

Deploy on Uvicorn

Uvicorn 是 ASGI 兼容的服务器(ASGI 表示异步网关接口)。由于 Pyramid 是基于 WSGI 的 Web 框架,因此我们需要使用 asgiref.wsgi 模块中定义的 WsgiToAsgi() 函数将 WSGI 应用程序对象转换为 ASGI 对象。

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。

pip3 install uvicorn

在 ASGI 模式下运行 Pyramid 应用程序。

uvicorn app:app

类似地,它可以使用 daphne 服务器提供服务。

daphne app:app