Python Pyramid - 快速指南



Python Pyramid - 概述

Pyramid 是一個用 Python 編寫的開源、符合 WSGI 的 Web 框架。最初該專案名為 Pylons,但後來以新名稱 Pyramid 釋出。

  • Pyramid 是一個極簡主義的 Web 框架。它沒有打包任何模板庫,也沒有支援任何特定的資料庫包。

  • 但是,它可以透過 SQLAlchemy 與 SQL 資料庫和 Zope 物件資料庫整合,以及其他 NoSQL 資料庫,如 CouchDB。

  • Pyramid 還可以配置為與 Mako、Jinja2 或 Chameleon 等模板庫一起工作。

  • Pyramid 由 **Chris McDonough** 開發。Pyramid 的第一個版本於 2011 年 1 月釋出。最新版本 Pyramid 2.0 於 2021 年 3 月釋出。

與其他 Python 框架的比較

Pyramid Web 應用程式框架受到 Zope 和 Django 框架的啟發。因此,它結合了這兩者的最佳規定。

  • Pyramid 主要基於 **repose.bfg** 框架。在與 Pylons 專案合併後,它於 2010 年更名為 Pyramid。

  • 擴充套件 Pyramid 應用程式的能力借鑑自 Zope 庫。無需修改應用程式程式碼,即可重用、修改或擴充套件應用程式。諸如宣告式安全層和路由遍歷等功能繼承自 Zope。

  • 與 Pylons 1.0 一樣,Pyramid 也不強制執行任何策略。它還允許使用者選擇任何資料庫或模板系統。URL 排程方法也受到 Pylons 的啟發。

  • **檢視**的概念基於 Django 的類似方法。廣泛的文件也是 Pyramid 借鑑的 Django 特性。

  • 雖然定義並不完全吻合,但可以認為 Pyramid 遵循 MVC(模型-檢視-控制器)方法。

Python Pyramid - 環境設定

建議在安裝了 Python 3.6 或更高版本的系統上安裝 Pyramid 包。Pyramid 可以安裝在 Linux、MacOS 和 Windows 平臺上。最簡單的安裝方法是使用 PIP 安裝程式,最好是在 Python 虛擬環境下。

pip3 install pyramid

雖然 Pyramid Web 應用程式可以使用內建的 WSGI 開發伺服器(它是 **wsgiref** 模組的一部分)執行,但不建議在生產環境中使用它。因此,我們還安裝 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

示例

要檢查 Pyramid 及其依賴項是否已正確安裝,請輸入以下程式碼並將其儲存為 **hello.py**,使用任何 Python 感知編輯器。

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 路由並將檢視函式繫結到它。從該配置物件獲得的 WSGI 應用程式物件是 **make_server()** 函式的引數,以及 localhost 的 IP 地址和埠。當呼叫 **serve_forever()** 方法時,伺服器物件進入監聽迴圈。

從命令終端執行此程式,如下所示。

Python hello.py

輸出

WSGI 伺服器開始執行。開啟瀏覽器並在位址列中輸入 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 - 應用程式配置

Pyramid 應用程式物件具有一個應用程式登錄檔,該登錄檔儲存檢視函式到路由的對映以及其他特定於應用程式的元件註冊。Configurator 類用於構建應用程式登錄檔。

Configurator 生命週期由上下文管理器管理,該管理器返回應用程式物件。

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

Configurator 類定義了以下重要方法來自定義應用程式 -

add_route()

此方法為 URL 排程註冊路由。使用以下引數 -

  • **name** - 第一個必需的位置引數必須是路由的唯一名稱。該名稱用於在註冊檢視或生成 URL 時識別路由。

  • **pattern** - 第二個必需的位置引數是表示 URL 路徑的字串,可選地包含用於從 URL 解析可變資料的變數佔位符。佔位符用花括號括起來。例如,“/students/{id}”。

  • **request_method** - 值可以是“GET”、“POST”、“HEAD”、“DELETE”、“PUT”之一。只有此型別的請求將與路由匹配。

add_view()

此方法將檢視配置新增到應用程式登錄檔。它將檢視函式繫結到配置中存在的 **route_name**。所需引數為 -

  • **view** - 檢視函式的名稱。

  • **route_name** - 一個字串,必須與路由配置宣告的名稱匹配。

  • **request_method** - 或者表示 HTTP REQUEST_METHOD 的字串(如“GET”、“POST”、“PUT”、“DELETE”、“HEAD”或“OPTIONS”),或者包含一個或多個這些字串的元組。

add_static_view()

此方法新增用於呈現靜態資源(如影像和 CSS 檔案)的 **view**,並使用以下引數 -

  • **name** - 此引數是表示應用程式相對本地 URL 字首或完整 URL 的字串。

  • **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 提供了另一種配置方法,稱為宣告式配置。

宣告式配置

有時,使用命令式程式碼進行配置變得很困難,尤其是在應用程式程式碼分佈在許多檔案中時。宣告式配置是一種便捷的方法。**pyramid.view** 模型定義了 **view_config** - 一個函式、類或方法裝飾器 - 它允許檢視註冊非常接近檢視函式本身的定義。

向 **@view_config()** 裝飾器提供了兩個重要的引數。它們是 **route_name** 和 **request_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() 函式添加了一個屬性,使其可供以後的掃描找到它。

示例

配置裝飾和掃描呼叫的組合統稱為宣告式配置。以下程式碼使用宣告式方法配置應用程式登錄檔。

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

輸出

執行以上程式後,WSGI 伺服器啟動。當瀏覽器訪問連結 https://:6543/ 時,"Hello World" 訊息將像以前一樣呈現。

Config

Python Pyramid - URL 路由

在 MVC 架構出現之前,Web 應用程式使用將使用者在瀏覽器中輸入的 URL 對映到程式檔案(其輸出作為 HTML 呈現,作為響應返回到瀏覽器)的機制。Pyramid 框架使用路由機制,其中 URL 的端點與應用程式登錄檔中註冊的不同 URL 模式匹配,呼叫其對映的檢視並呈現響應。

一個典型的 URL 包含三個部分:協議(例如 http:// 或 https://),後跟 IP 地址或主機名。主機名之後第一個 / 之後的 URL 的剩餘部分稱為路徑或端點。

Mysite

端點後跟一個或多個可變部分構成路由。可變部分識別符號用花括號括起來。例如,對於上述 URL,路由為 /blog/{id}

WSGI 應用程式充當路由器。它根據路由對映中存在的 URL 模式檢查傳入的請求。如果找到匹配項,則執行其關聯的檢視可呼叫物件並返回響應。

路由配置

透過呼叫 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')

示例

我們將這些語句放在下面的程式中:

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

輸出

執行以上程式碼並在瀏覽器中訪問 https://: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 字母、下劃線和數字。

路由匹配

當傳入的請求與特定路由配置關聯的 URL 模式匹配時,名為 matchdict 的字典物件將作為請求物件的屬性新增。

request.matchdict 包含與模式元素中的替換模式匹配的值。matchdict 中的鍵是字串,而它們的值是 Unicode 物件。

在前面的示例中,將 `index()` 檢視函式更改為以下內容:

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

瀏覽器以 dict 物件的形式顯示路徑引數。

Parameters

當請求與路由模式匹配時,傳遞給檢視函式的請求物件還包含一個 matched_route 屬性。匹配路由的名稱可以從其名稱屬性中獲取。

示例

在以下示例中,我們使用 `@view.config()` 裝飾器定義了兩個檢視函式 `student_view()` 和 `book_view()`。

應用程式的登錄檔配置為具有兩個相應的路由 - 'student' 對映到 '/student/{name}/{age}' 模式,'book' 對映到 '/book/{title}/{price}' 模式。我們呼叫 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()

輸出

當瀏覽器給出 https://:6543/student/Ravi/21 URL 時,輸出為

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

如果輸入的 URL 為 https://:6543/book/Python/300,則輸出為

Title: Python, Price: 300

Python Pyramid - 檢視配置

"檢視配置"一詞是指將檢視可呼叫物件(函式、方法或類)與路由配置資訊關聯的機制。Pyramid 找到給定 URL 模式的最佳可呼叫物件。

有三種方法可以配置 檢視

  • 使用 `add_view()` 方法

  • 使用 `@view_config()` 裝飾器

  • 使用 `@view_defaults()` 類裝飾器

使用 `add_view()` 方法

這是透過呼叫 Configurator 物件的 add_view() 方法以命令方式配置檢視的最簡單方法。

此方法使用以下引數:

  • name - 匹配此檢視可呼叫物件所需的檢視名稱。如果未提供名稱,則使用空字串(表示預設檢視)。

  • context - 為了找到並呼叫此檢視,此資源必須是 Python 類的一個物件。如果未提供上下文,則使用值 None,它匹配任何資源。

  • route_name - 此值必須與路由配置宣告的名稱匹配,在呼叫此檢視之前必須匹配。如果提供了 `route_name`,則只有在命名路由匹配時才會呼叫檢視可呼叫物件。

  • request_type - 請求為了找到並呼叫此檢視必須提供的介面。

  • request_method - 表示 HTTP REQUEST_METHOD 的字串(例如 "GET"、"POST"、"PUT"、"DELETE"、"HEAD" 或 "OPTIONS")或包含這些字串中一個或多個的元組。只有當請求的 `method` 屬性與提供的值匹配時,才會呼叫檢視。

  • request_param - 此引數可以是任何字串或字串序列。只有當 `request.params` 字典具有與提供的值匹配的鍵時,才會呼叫檢視。

示例

在以下示例中,定義了兩個函式 getview()postview(),並將其與兩個相同名稱的路由關聯。這些函式僅返回呼叫它們的 HTTP 方法的名稱。

當使用 GET 方法請求 URL /get 時,呼叫 `getview()` 函式。類似地,當透過 POST 方法請求 /post 路徑時,執行 `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 https://: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'])

輸出

CURL 輸出如下所示:

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

使用 `@view_config()` 裝飾器

與其以命令方式新增檢視,不如使用 `@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')
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()` 方法後,檢視才會新增到應用程式配置中。雖然消除了以命令方式新增檢視的需要,但效能可能會稍慢一些。

輸出

`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()` 方法來新增檢視,而不是掃描檢視配置。

示例

如果類中的方法必須與不同的路由關聯,則應在每個方法的頂部使用單獨的 `@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()

輸出

以下是 CURL 命令的輸出:

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

使用 `@view_defaults()` 裝飾器

view_defaults() 是一個類裝飾器。如果您必須將類中的方法作為檢視新增,並帶有一些公共引數和一些特定引數,則可以在類的頂部指定 view_defaults() 裝飾器中的公共引數,並在每個方法之前使用單獨的 view_config() 執行每個方法的配置。

示例

在以下程式碼中,我們有不同的方法響應相同的路由,但使用不同的 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()

輸出

使用不同 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 https://:6543/view
hello POST
C:\Users\Acer>curl -d "param1=value1" -H "Content-Type: application/json" -X PUT https://:6543/view
hello PUT
C:\Users\Acer>curl -X DELETE https://:6543/view
hello DELETE

Python Pyramid - 路由字首

很多時候,類似的 URL 模式在多個 Python 程式碼模組中使用不同的路由註冊。例如,我們有一個 student_routes.py,其中 /list 和 /add URL 模式分別與 'list' 和 'add' 路由註冊。與這些路由關聯的檢視函式分別為 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/ 分別註冊到 'show' 和 'new' 路由。它們關聯的檢視分別為 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()` 的第一個引數是新增路由的函式,第二個引數是將附加到包含函式中使用的 URL 模式的 `route_prefix` 字串。

因此,語句

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

將導致 '/list' URL 模式更改為 '/student/list',而 '/add' 則變為 'student/add'。類似地,我們可以在 `books()` 函式中為這些 URL 模式新增字首。

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

示例

啟動伺服器的程式碼如下所示:

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

輸出

讓我們執行以上程式碼並透過以下 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 - 模板

預設情況下,檢視函式響應的 `content-type` 為純文字。為了呈現 HTML,響應正文的文字可以包含 HTML 標籤,如以下示例所示:

示例

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

輸出

啟動伺服器(透過執行以上程式碼)後,訪問 https://:6543/,瀏覽器呈現以下輸出:

Templates

但是,這種呈現 HTML 的方法,尤其是在可能包含某些可變資料的情況下,極其繁瑣。為此,Web 框架使用模板庫。模板庫將可變資料與其他靜態 HTML 程式碼合併,以動態生成和呈現網頁。

模板繫結

Pyramid 透過繫結到流行的模板庫(如 jinja2、Mako 和 Chameleon)來提供模板支援。

模板語言 Pyramid 繫結 預設副檔名
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 指令碼另存為 templates 資料夾中的 hello.jinja2

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

Jinja2 模板庫

這裡,'name' 是一個 jinja2 模板變數。jinja2 模板語言使用以下語法在 HTML 指令碼中插入變數和程式設計結構:

表示式

  • {{ ... }} 用於將表示式列印到模板輸出。

  • {% ... %} 用於語句。

  • {# ... #} 用於不包含在模板輸出中的註釋。

條件語句

  • {% if expr %}

  • {% else %}

  • {% endif %}

迴圈

  • {% for var in iterable %}

  • {% endfor %}

在 hello.jinja2 {{ name }} 中,'name' 上下文變數的值在檢視響應中動態呈現。

渲染模板

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)

示例

像往常一樣,此檢視新增到 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()

輸出

執行伺服器並訪問 https://: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)

其餘程式碼保持不變,瀏覽器也顯示與上面相同的輸出。

透過配置進行渲染

如前所述,Pyramid 的檢視可呼叫返回的 HTTP 響應的 content_type 為 text/plain。但是,如果 @view_config 裝飾器的 renderer 引數被分配了這些值中的任何一個,它可以更改為字串、JSON 或 JSONP。因此,Pyramid 具有以下內建渲染器:

  • JSON

  • 字串

  • JSONP

示例

在以下示例中,hello_world() 檢視函式配置為渲染 JSON 響應。

from pyramid.view import view_config

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

輸出

將渲染器型別設定為 JSON 還會將 HTTP 響應的 content_type 標頭設定為 application/json。瀏覽器顯示 JSON 響應,如下所示:

JSON

@view_config() 裝飾器的 renderer 引數可以設定為模板網頁(必須存在於 templates 資料夾中)。先決條件是必須安裝模板庫的相應 Python 繫結,並且應用程式配置必須包含該繫結。

我們已經安裝了 python_jinja2 包,這樣我們就可以使用 jinja2 模板由 hello_world() 檢視函式渲染,該函式由 @view_config() 裝飾,並帶有 renderer 引數。

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

示例

在這種情況下,檢視函式返回一個字典物件。它作為上下文資料提供給模板,可以使用模板語言語法元素插入 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()

輸出

view 函式提供的變數資料的模板網頁如下所示:

View

新增/更改渲染器

模板只不過是散佈著模板語言語法的網頁。即使 Pyramid 使用 .jinja2 作為 jinja2 模板的預設副檔名,但既定的做法是使用網頁的 .html 副檔名。

我們可以更改應用程式配置以允許除了 .jinja2 之外還使用 .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()

來自 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']}

示例

修改後的應用程式程式碼如下:

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

輸出

啟動伺服器,開啟瀏覽器並輸入 URL https://:6543/Tutorialspoint。尾隨字串成為 matchdict 中 'name' 鍵的值。它被 jinja2 模板利用,並呈現以下輸出。

Jinja2

模板中的條件語句和迴圈

jinja2 模板語言允許在 HTML 指令碼中包含條件語句和迴圈結構。這些程式設計元素的 jinja2 語法如下:

條件語句

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

迴圈

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

可以看出,jinja2 語法與 Python 的 if 和 for 語句非常相似。除了 jinja2 不使用縮排標記塊之外。相反,對於每個 if 都必須有一個 endif 語句。類似地,對於每個 for 語句,都必須有一個 endfor 語句。

示例

以下示例演示了模板條件語句和迴圈語句的使用。首先,Pyramid 程式碼使用 students 作為字典物件的列表,每個字典都包含學生的 id、name 和百分比。此列表物件作為上下文傳遞給 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>

輸出

執行 marklist.py 程式碼。https://:6543/ 連結呈現以下表格結果:

Marklist

Python Pyramid - HTML 表單模板

在本章中,我們將瞭解 Pyramid 如何從 HTML 表單讀取資料。讓我們將以下 HTML 指令碼另存為 myform.html。我們將使用它來獲取 Template 物件並渲染它。

<html>
<body>
   <form method="POST" action="https://: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 網頁模板與上一示例中使用的相同。它顯示學生資料的表格以及計算結果列。

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

示例

包含用於渲染 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()

輸出

要啟動伺服器,請從命令列執行上述 Python 程式碼。在您的瀏覽器中,訪問 https://:6543/ 以獲取如下所示的表單:

Student

輸入樣本資料(如所示)並按提交按鈕。瀏覽器將定向到 /students URL,該 URL 又會呼叫 add() 檢視。結果是一個成績單表格,顯示新輸入的新學生的​​資料。

Student URL

Python Pyramid - 靜態資源

通常需要在模板響應中包含一些即使存在某些動態資料也不會更改的資源。此類資源稱為靜態資產。媒體檔案(.png、.jpg 等)、用於執行某些前端程式碼的 JavaScript 檔案或用於格式化 HTML 的樣式表(.css 檔案)是靜態檔案的示例。

Pyramid 將這些靜態資產從伺服器檔案系統中的指定目錄提供給客戶端的瀏覽器。Configurator 物件的 add_static_view() 方法定義了路由的名稱和包含靜態檔案(如影像、JavaScript 和 CSS 檔案)的資料夾的路徑。

按照慣例,'static' 目錄用於儲存靜態資產,並且 add_static_view() 的用法如下:

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

定義靜態路由後,可以在 HTML 指令碼中使用時透過 request.static_url() 方法獲取靜態資產的路徑。

靜態影像

在以下示例中,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>

示例

應用程式程式碼使用 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()

輸出

執行上述程式碼以啟動伺服器。使用 https://:6543/Guest 作為瀏覽器中的 URL。這裡 'Guest' 是檢視函式在 matchdict 物件中獲取的路徑引數,並作為上下文傳遞給 logo.html 模板。瀏覽器現在顯示 Pyramid 徽標。

Pyramid

Javascript 作為靜態資產

這是另一個靜態檔案的示例。一個 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>

示例

儲存在 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;
}

輸出

該函式檢測當前時間的值,並根據一天中的時間為 msg 變數分配相應的值(早上好、下午好或晚上好)。

hello.js 儲存到 static 資料夾中,將 hello.html 儲存到 templates 資料夾中,然後重新啟動伺服器。瀏覽器應顯示當前時間及其下方的相應訊息。

Good Evening

Python Pyramid - 請求物件

檢視可呼叫的功能包括從 WSGI 環境獲取請求資料,並在處理後將某個 HTTP 響應返回給客戶端。

通常,此物件不是由使用者例項化的。相反,它封裝了 WSGI environ 字典。此 request 物件表示“pyramid.request.Request 類”。它擁有許多屬性和方法,檢視函式使用這些屬性和方法來處理請求資料。

以下是一些屬性

  • request.method - 客戶端用於傳送資料的 HTTP 請求方法,例如 GET、POST

  • request.GET - 此屬性是一個 multidict,包含查詢字串中的所有變數。

  • request.POST - 此屬性僅在請求為 POST 且為表單提交時可用。它是一個 multidict,包含請求正文中的所有變數。

  • request.params - request.GET 和 request.POST 中所有內容的組合 multidict。

  • request.body - 此屬性包含整個請求正文作為字串。當請求為非表單提交的 POST 或 PUT 等請求時,這很有用。

  • request.cookies - 包含所有 cookie。

  • request.headers - 所有 HTTP 標頭的區分大小寫的字典。

除了上述 HTTP 特定的環境屬性之外,Pyramid 還添加了一些特殊的屬性。

  • request.url − 返回包含查詢字串的完整請求 URL,例如:https://:6543/app?name=Ravi

  • request.host − URL 中的主機資訊,例如:localhost

  • request.host_url − 此屬性返回包含主機的 URL,例如:https://:6543/

  • request.application_url − 應用程式的 URL(不包含 PATH_INFO),例如:https://:6543/app

  • request.path_url − 包含應用程式的 URL,包括 PATH_INFO,例如:https://:66543/app

  • request.path − 返回包含 PATH_INFO 的 URL,不包含主機,例如:"/app"

  • request.path_qs − URL 中的查詢字串,包括 PATH_INFO,例如:"/app?name=Ravi"

  • request.query_string − 僅 URL 中的查詢字串,例如:"name=Ravi"

Python Pyramid - 響應物件

Response 類定義在 pyramid.response 模組中。此類的物件由檢視可呼叫物件返回。

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

響應物件包含狀態程式碼(預設為 200 OK)、響應頭列表和響應正文。大多數 HTTP 響應頭都可以作為屬性使用。以下屬性可用於 Response 物件 -

  • response.content_type − 內容型別是一個字串,例如 - response.content_type = 'text/html'。

  • response.charset − 它還告知 response.text 中的編碼。

  • response.set_cookie − 此屬性用於設定 Cookie。需要提供的引數是名稱、值和 max_age。

  • response.delete_cookie − 從客戶端刪除 Cookie。實際上,它將 max_age 設定為 0,並將 Cookie 值設定為 ''。

pyramid.httpexceptions 模組定義了用於處理錯誤響應(例如 404 未找到)的類。這些類實際上是 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='https://:6543/')

Python Pyramid - 會話

會話是指客戶端登入伺服器到登出伺服器之間的時間間隔。會話物件也是一個字典物件,包含會話變數及其關聯值的鍵值對。在 Pyramid 中,它作為請求物件的屬性可用。

為了處理會話機制,Pyramid 應用程式物件必須配置為使用會話工廠來返回會話物件。Pyramid 核心提供了一個基本的會話工廠,它使用 Cookie 來儲存會話資訊。

預設會話工廠

pyramid.session 模組定義了 SignedCookieSessionFactory 類。它的物件需要一個用於對會話 Cookie 資訊進行數字簽名的金鑰。

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

Configurator 類的 set_session_factory() 方法使用此工廠物件來設定會話。

config.set_session_factory(my_session_factory)

完成此操作後,會話物件現在可用於實現,作為 request.session 屬性。要新增會話變數,請使用 -

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

要檢索會話變數,請使用 -

user=request.session['user']

要刪除會話變數,請使用 pop() 方法。

request.session.pop('user')

會話示例

下面描述了在 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() 函式讀取“user”表單屬性,並使用其值新增會話變數。

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

示例

完整的程式碼如下 -

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 虛擬環境資料夾中的子資料夾(稱為“session”)中的 main.py。請注意,此子資料夾必須有一個空的 __init__.py 檔案才能被視為包。

輸出

執行 main.py 並輸入 https://:6543/ 以在瀏覽器中開啟登入表單。

Submit

輸入使用者名稱並按下“提交”按鈕。給定的名稱將儲存為“user”會話變數。

Session

“點選此處”連結讀回 session 變數並顯示歡迎訊息。

Welcome Admin

登出連結彈出 session 變數並將瀏覽器帶回登入頁面。

Python Pyramid - 事件

Pyramid 應用程式在其生命週期過程中會發出各種事件。儘管通常不需要使用這些事件,但可以透過正確處理這些事件來執行稍微高階的操作。

只有當您將其與訂閱者函式註冊時,Pyramid 框架廣播的事件才能使用。發出的事件必須用作 subscriber 函式的引數。

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

但是,只有當訂閱者函式使用 add_subscriber() 方法新增到應用程式的配置中時,它才會生效,如下所示 -

在以下程式碼段中,應用程式配置為在發出 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 模組中可用。它們列在下面 -

  • ApplicationCreated − 此事件在呼叫 Configurator 類的 config.make_wsgi_app() 方法返回 WSGI 應用程式物件時傳輸。

  • NewRequest − 每次 Pyramid 應用程式開始處理傳入請求時,都會發出此事件類的物件。此物件具有一個 request 屬性,該屬性是 WSGI 環境字典提供的請求物件。

  • ContextFound − 應用程式的路由器遍歷所有路由,並找到與 URL 模式的匹配項。此時會例項化 ContextFound 類物件。

  • BeforeTraversal − 在 Pyramid 路由器嘗試查詢路由物件但尚未執行任何遍歷或檢視程式碼之前,會將此類的例項作為事件發出。

  • NewResponse − 顧名思義,每當任何 Pyramid 檢視可呼叫物件返回響應時,都會引發此事件。此物件具有 request 和 response 屬性。

  • BeforeRender − 在呼叫渲染器之前,會將此型別的物件作為事件傳輸。此事件的訂閱者函式可以訪問應用程式的全域性資料(以 dict 物件的形式存在),並且可以修改一個或多個鍵的值。

Python Pyramid - 訊息閃現

訊息閃現機制由 Web 應用程式框架使用,以向用戶提供有關其與應用程式互動的某些反饋。閃現的訊息由會話物件儲存在佇列中。

閃現訊息機制使得可以在一個檢視中建立訊息,並在下一個呼叫的檢視函式中渲染它。與上一節一樣,我們必須首先啟用會話工廠才能處理會話。要在訊息佇列中新增訊息,請使用會話物件的 flash() 方法。

request.session.flash('Hello World')

會話具有 pop_flash()peek_flash() 方法。pop_flash() 方法從佇列中刪除最後新增的訊息。peek_flash() 方法如果佇列中有訊息則返回 true,如果佇列為空則返回 false。

這兩種方法都用於模板網頁中,從佇列中獲取一條或多條訊息,並將其渲染為響應的一部分。

訊息閃現示例

下面的示例演示了訊息閃現機制。在這裡,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'> 部分彈出每條訊息。

以下是 index.html 的指令碼,它閃現由 login() 檢視插入的成功訊息 -

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

示例

此示例的應用程式程式碼為 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()

將此程式程式碼另存為 Pyramid 虛擬環境中的 flash 子資料夾中的 app.py,並在其中放置一個空白的 __init__.py。將兩個模板(“index.html”和“login.html”)儲存在 flush\templates 資料夾中。

輸出

執行 main.py 並透過單擊 https://:6543/login 連結在瀏覽器中開啟登入表單。

Pyramid Message

嘗試輸入預留使用者名稱之一“admin”、“manager”或“supervisor”。錯誤訊息將按如下所示閃現 -

Weak Password

這次,輸入可接受的憑據並檢視結果 -

Loggedin

Python Pyramid - 使用 SQLAlchemy

在本章中,我們將學習如何使用關係資料庫作為 Pyramid Web 應用程式的後端。Python 可以使用相應的與 DB-API 相容的聯結器模組或驅動程式與幾乎所有關係資料庫進行互動。但是,我們將使用 SQLAlchemy 庫作為 Python 程式碼和資料庫之間的介面(我們將使用 SQLite 資料庫,因為 Python 對其具有內建支援)。SQLAlchemy 是一個流行的 SQL 工具包和物件關係對映器。

物件關係對映是一種程式設計技術,用於在面向物件程式語言中轉換不相容型別系統之間的資料。通常,面嚮物件語言(如 Python)中使用的型別系統包含非標量型別。但是,大多數資料庫產品(如 Oracle、MySQL 等)中的資料型別都是基本型別,例如整數和字串。

在 ORM 系統中,每個類都對映到基礎資料庫中的一個表。ORM 會為您處理這些問題,而您無需自己編寫繁瑣的資料庫介面程式碼,這樣您就可以專注於系統邏輯的程式設計。

為了使用 SQLALchemy,我們需要首先使用 PIP 安裝程式安裝庫。

pip install sqlalchemy

SQLAlchemy 旨在與為特定資料庫構建的 DBAPI 實現一起使用。它使用方言系統與各種型別的 DBAPI 實現和資料庫進行通訊。所有方言都需要安裝相應的 DBAPI 驅動程式。

以下列出了包含的方言:

  • Firebird

  • Microsoft SQL Server

  • MySQL

  • Oracle

  • PostgreSQL

  • SQLite

  • Sybase

資料庫引擎

由於我們將使用 SQLite 資料庫,因此需要為名為test.db的資料庫建立一個數據庫引擎。從sqlalchemy模組匯入create_engine()函式。

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

為了與資料庫互動,我們需要獲取其控制代碼。會話物件是資料庫的控制代碼。Session 類使用sessionmaker()定義 - 一個可配置的會話工廠方法,它繫結到引擎物件。

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

接下來,我們需要一個宣告性基類,用於在宣告性系統中儲存類的目錄和對映的表。

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

模型類

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

現在我們將定義檢視函式,用於對上述資料庫中的學生表執行 CRUD 操作(即新增、顯示、修改和刪除行)。

新增新的學生記錄

首先,我們將建立一個 HTML 表單模板,供使用者輸入學生資料,並定義一個渲染該模板的檢視。以下是myform.html模板

示例

<html>
<body>
   <form method="POST" action="https://: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='https://:6543/')

確保在配置中添加了 add 路由,並將其對映到 /add URL 模式。

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

輸出

如果我們啟動伺服器並在瀏覽器中開啟https://:6543/new,則將顯示如下所示的輸入表單:

Student Details

填寫表單並按下“提交”按鈕。將呼叫 add() 檢視,並在 students 表中新增新記錄。重複此過程幾次以新增一些記錄。以下是一個示例資料:

Student Database

顯示所有記錄的列表

透過查詢模型獲取 Students 模型的所有物件(對應於 students 表中的行)。

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

將每一行轉換為 dict 物件,將所有這些物件追加到 dict 物件列表中,並將其作為上下文返回到 list.html 模板,以便以 HTML 模板的形式顯示。此過程由與 list 路由關聯的 showall() 檢視函式執行。

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

示例

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="https://:6543/new">Add new</a></h3>
   </body>
</html>

在配置中新增 list 路由,並將其註冊到“/”URL。

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

輸出

啟動伺服器後,在瀏覽器中開啟https://:6543/。將顯示 students 表中現有記錄的列表。

Add New

請注意最後兩列中的超連結。例如,"id=1"之前的“編輯”連結指向https://:6543/show/1。這些連結旨在執行更新和刪除操作。

更新現有記錄

在 /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}

示例

showform.html 模板的 HTML/jinja2 程式碼如下所示:

<html>
<body>
   <form method="POST" action="https://: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>

輸出

讓我們更新 id=3 的記錄。單擊相應的“編輯”連結導航到https://:6543/show/3

Percentage

更改 marks 文字欄位中的值並按下提交。表單將重定向到 /update URL,並呼叫 update() 檢視。它獲取提交的資料並更新相應的物件,從而也更新 students 表中的底層行。

@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='https://:6543/')

return 語句將瀏覽器重定向回“/”URL,該 URL 指向 list() 函式並顯示更新後的 marklist。

Updated Marklist

在執行之前,請確保已將 update 路由新增到配置中。

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

刪除記錄

要刪除與 marklist 表中某一行對應的記錄,請遵循最後一列中的“刪除”連結。例如,單擊第 3 行中的“刪除”會發出https://: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'}

示例

刪除與從 URL 解析的路徑引數對應的物件,並由以下模板- deleted.html呈現相應的訊息:

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

顯然,必須在應用程式配置登錄檔中新增 delete 路由。

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

輸出

記錄刪除操作的結果如下所示:

Record

執行上述說明的活動,請執行以下步驟:

  • 在 Pyramid 虛擬環境中建立一個名為testapp的資料夾

  • testapp內部,建立templates資料夾。

  • 在 testapp 內部建立一個空白的__init__.py,使其成為一個包。

  • 將 marklist.html、myform.html、showform.html 和 deleted.html 檔案放入“testapp\templates”資料夾中。這些檔案的程式碼已在上面給出。

  • 將以下程式碼另存為testapp中的models.py

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
  • 將以下程式碼另存為testapp資料夾中的views.py

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='https://: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='https://: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'}
  • 將以下程式碼另存為 testapp 資料夾中的 main.py。

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()    
  • 從命令提示符執行main.py

Python main.py
  • 在瀏覽器視窗中使用https://:6543/ URL。將顯示一個只有標題且沒有記錄的表格。

  • 按照表格下方的“新增新”連結新增記錄。

  • 單擊表格中的“編輯”連結更新記錄。

  • 單擊表格中的“刪除”連結刪除選定的記錄。

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 - 建立專案

假設 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包子資料夾和 tests 包。內部testproj子資料夾是一個包,包含 models 和 scripts、子包以及 static 和 templates 資料夾。

接下來,使用 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 實用程式還在 tests 包中生成測試套件。它們基於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

開啟瀏覽器並在其中訪問https://:6543/。將顯示新建立專案的首頁,如下所示:

Cookiecutter

除錯工具欄

您可以在主頁的右上方找到一個較小的 Pyramid 徽標。單擊它以開啟一個新標籤頁和一個除錯工具欄,該工具欄提供有關該專案的許多有用資訊。

例如,歷史記錄標題下的 SQLAlchemy 選項卡顯示 SQLAlchemy 查詢,顯示從development.ini中的預設資料建立的模型的結構。

Pyramid logo

全域性標題再次顯示諸如 Introspection、Routes 等選項卡,如下所示。單擊“Routes”選項卡以檢視應用程式配置中定義的路由及其匹配模式。

Debug Toolbar

Python Pyramid - 專案結構

如前所述,外部 testproj 資料夾包含 testproj 和 test 包。此外,它還包含其他用於描述、執行和測試應用程式的檔案。這些檔案是:

  • MANIFEST.in包含要包含在包源分發中的檔案列表。

  • development.ini 是一個 PasteDeploy 配置檔案,可用於在開發過程中執行您的應用程式。

  • production.ini 是一個 PasteDeploy 配置檔案,可用於在生產環境中執行您的應用程式。

  • pytest.ini 是一個用於執行測試的配置檔案。

  • setup.py 是標準的 Setuptools setup.py 檔案,用於測試和分發應用程式。

  • 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 應用程式物件 main 的名稱。它在 textproj 包的 __init__.py 檔案(在 testproj 專案資料夾內)中宣告。此部分包含其他啟動時配置設定。

例如,“pyramid.includes” 設定指定要在執行時包含的包。在上面的示例中,包含了 debugtoolbar 包,以便在單擊 Pyramid 徽標時啟用除錯面板。我們在前面的部分中看到了它的功能。

我們還看到,此應用程式中要使用的資料庫的 URL 也已指定。

[server:main] 部分指定偵聽 TCP 埠 6543 的 WSGI 伺服器的配置。它配置為僅偵聽本地主機 (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 - 包結構

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() 函式新增到應用程式配置中。

檢視包

專案包(在我們的例子中是 testproj 包)包含此檢視子包 - 一個包含空白 __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 指令碼還匯入 models 子包中 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 {}

靜態資料夾

testproj 包資料夾下的此資料夾包含 Pyramid 徽標檔案和主頁的 theme.CSS。

模板資料夾

我們知道 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 %}

模型包

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 - 手動建立專案

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 資訊中的應用程式物件,以及偵聽本地主機 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 路由,並返回由 Configurator 的 make_wsgi_app() 方法給出的應用程式物件。

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 - 命令列 Pyramid

Pyramid 庫有一個 scripts 子包,它包含許多可用於控制和檢查 Pyramid 應用程式的 Python 指令碼。這些模組既可以作為可匯入的模組使用,也可以從命令提示符使用。因此,它們通常被稱為命令列指令碼。

這些命令列指令碼是:

  • pserve - 為使用 PasteDeploy 配置檔案的 Web 應用程式提供服務。

  • pviews - 顯示給定 URL 的匹配檢視。

  • pshell - 互動式 Shell。

  • proutes - 顯示所有應用程式路由。

  • ptweens - 顯示“Tweens”。

  • prequest - 呼叫請求。

  • pdistreport - 顯示所有已安裝的發行版及其版本。

所有這些命令列指令碼都使用 PasteDeploy 配置檔案 (development.ini)。

pserve

這是最重要的指令碼。使用選定的伺服器 (Waitress) 和提到的主機和埠 (localhost:6543) 為“development.ini” [app:main] 部分中配置的 Pyramid 應用程式提供服務。

假設 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 實用程式更靈活,可以使用以下命令列引數:

  • config_uri - 配置檔案的 URI。

  • -n <name> - 載入命名的應用程式(預設值為 main)。

  • -s <server_type> - 使用命名的伺服器。

  • --server-name <section_name> - 使用配置檔案中定義的命名伺服器(預設值:main)

  • --reload - 使用自動重啟檔案監視器。

  • -b - 在 Web 瀏覽器中開啟伺服器 URL。

應用程式在 https://:6543 上提供服務,在這種情況下,訪問受到限制,因此只有在同一臺機器上執行的瀏覽器才能訪問。如果要讓同一網路上的其他機器訪問,則編輯“development.ini”檔案,並將 [server:main] 部分中的偵聽值替換為如下所示:

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

設定 *:6543 等效於 0.0.0.0:6543 [::]:6543,因此,應用程式響應系統擁有的所有 IP 地址上的請求,而不僅僅是本地主機的請求。

pserve 命令列中的 --reload 選項會導致應用程式在修改正在執行的程式碼時自動重新載入。

使用 --reload 選項啟動應用程式。

pserve development.ini --reload
Starting monitor for PID 36224.
Starting server in PID 36224.
Serving on https://:6543
Serving on https://:6543

如果對專案的 .py 檔案或 .ini 檔案進行了任何更改,伺服器將自動重新啟動:

testproj/development.ini changed; reloading ...
Gracefully killing the server.
Starting monitor for PID 36286.
Starting server in PID 36286.
Serving on https://:6543
Serving on https://: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,並在其下方顯示所有匹配的檢視及其檢視配置詳細資訊。在此示例中,只有一個檢視匹配,因此只有一個 View 部分。

pshell

pshell 指令碼使能夠從 Python 提示符與 Pyramid 應用程式的環境進行互動。此 Shell 使用 PasteDeploy 配置檔案,即 development.ini 作為命令列引數(如其他 Pyramid 指令碼),並開啟 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>

甚至可以使用在 models.py 中宣告的 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 使用事務管理器物件 tm,該物件在 pyramid_tm 包中宣告。為了確認添加了新記錄,請將其取回。

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

也可以透過在 SQLite GUI 工具中實際檢視資料庫的 models 表來確認這一點。

SQLite

prequest

prerequisite 工具允許您測試 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 - 測試

編寫測試指令碼以確保您的程式碼正常工作被認為是一種良好的程式設計實踐。Python 生態系統有許多測試框架,包括標準庫中捆綁的 **unittest**。**Pytest** 是一個流行的測試庫。它是 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 ===========================

功能測試

儘管單元測試在測試驅動開發 (TDD) 方法中得到了廣泛使用,但對於 Web 應用程式,**WebTest** 是一個執行功能測試的 Python 包。我們可以模擬針對 WSGI 應用程式的完整 HTTP 請求,然後測試響應中的資訊。

示例

讓我們使用我們在前面示例中使用的 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)

輸出

最後,根據以下命令執行 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 ===========================

Cookiecutter 專案中的測試

CookieCutter 實用程式自動生成包含功能測試和單元測試的 tests 包。我們之前使用 Cookiecutter 構建了一個名為 testproj 的 Pyramid 專案。在這個專案中,我們找到了 tests 資料夾。

示例

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

輸出

這些測試由以下命令執行 -

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 - 日誌記錄

為了收集有關應用程式的有用資訊,Pyramid 使用 Python 標準庫中的 **logging** 模組。它在開發和生產模式下都非常有用,可以在應用程式執行期間檢測任何問題。應用程式日誌可以包含您自己的訊息以及來自第三方模組的訊息。

記錄的訊息具有以下預定義型別(按嚴重性降序排列) -

  • CRITICAL
  • ERROR
  • WARNING
  • INFO
  • DEBUG
  • NOTSET

預設情況下,日誌訊息被重定向到 sys.stderr 流。要開始收集日誌訊息,我們需要宣告一個 Logger 物件。

import logging
log = logging.getLogger(__name__)

現在可以使用與所需日誌級別相對應的 logger 方法生成日誌訊息。要生成一條對除錯應用程式有用的訊息,請使用帶有適當訊息字串的 **log.debug()** 訊息。

基於 PasteDeploy 配置的 Pyramid 應用程式使啟用日誌支援變得非常容易。PasteDEploy 檔案(development.ini 和 production.ini)使用日誌模組配置引數中使用的 **ConfigParser** 格式。development.ini 中與日誌相關的部分在 pserve 命令呼叫時傳遞給日誌模組的配置過程。

配置檔案中的各種 logger 部分指定應用程式物件的鍵、格式和 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** 檔案中。

示例

接下來,宣告 Logger 物件並在 **hello_world()** 函式中放置一條除錯訊息。這是 **__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

當在瀏覽器中輸入 **https://: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

輸出

由於在配置中啟用了除錯工具欄,因此它會顯示在瀏覽器中 -

Hello TP

除錯訊息也顯示在除錯工具欄的日誌選項卡上,如下所示 -

Log Msgs

Python Pyramid - 安全性

Pyramid 的宣告式安全系統確定當前使用者的身份並驗證使用者是否可以訪問某些資源。安全策略可以阻止使用者呼叫檢視。在呼叫任何檢視之前,授權系統使用請求中的憑據來確定是否允許訪問。

安全策略定義為一個類,該類透過 **pyramid.security** 模組中定義的以下方法控制使用者訪問 -

  • **forget(request)** - 此方法返回適合“忘記”當前已認證使用者擁有的憑據集的標頭元組。它通常在檢視函式的主體中使用。

  • **remember(request, userid)** - 此方法在請求的響應上返回一系列標頭元組。它們適合使用當前安全策略“記住”一組憑據,例如 userid。常見的用法可能在檢視函式的主體中如下所示。

已認證使用者的訪問由此模組中的 **Allowed** 和 **Denied** 類的物件控制。

為了實現身份、記住和忘記機制的功能,Pyramid 提供了以下在 **pyramid.authentication** 模組中定義的 **helper** 類 -

  • **SessionAuthenticationHelper** - 將 userid 儲存在會話中。

  • **AuthTktCookieHelper** - 使用“身份驗證票證”cookie 儲存 userid。

我們還可以使用 **extract_http_basic_credentials()** 函式使用 HTTP 基本身份驗證檢索使用者憑據。

要從 WSGI 環境中的 REMOTE_USER 中檢索 userid,可以使用 **request.environ.get('REMOTE_USER')**。

示例

現在讓我們學習如何透過以下示例實現安全策略。“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)

我們包資料夾中的 **__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 列表中的內容進行驗證時,“記住”這些詳細資訊。另一方面,登出檢視透過“忘記”釋放這些詳細資訊。

主頁檢視呈現以下 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

輸出

開啟瀏覽器並訪問 **https://:6543/** 連結。

Welcome

單擊“登入”連結以開啟登入表單 -

Login

主頁會返回,連結更改為登出,因為憑據被記住。

Hello

單擊“登出”連結將導致忘記憑據,並顯示預設主頁。

Python Pyramid - 部署

本教程中迄今為止開發的 Pyramid 應用程式示例已在本地計算機上執行。要使其公開訪問,必須將其部署在支援 WSGI 標準的生產伺服器上。

為此,可以使用許多與 WSGI 相容的 http 伺服器。例如 -

  • waitress
  • paste.httpserver
  • CherryPy
  • uWSGI
  • gevent
  • mod_wsgi

我們已經討論瞭如何使用 Waitress 伺服器來託管 Pyramid 應用程式。它可以在具有公共 IP 地址的機器的 80(HTTP)和 443(HTTPS)埠上提供服務。

mod_wsgi

Apache 伺服器是一種流行的開源 HTTP 伺服器軟體,由 Apache 軟體基金會發布。它為網際網路上大多數 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"

這些是需要合併到 Apache 配置檔案中的 mod_wsgi 模組設定。開啟 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 應用程式。

部署到 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
廣告