教程?

本教程將指導您學習基本但完整的Cherrypy應用程序,這些應用程序將向您展示常見的概念以及稍微高級一些的概念。

教程1:基本的Web應用程序?

下面的示例演示了可以用cherrypy編寫的最基本的應用程序。它啟動一個服務器并托管一個應用程序,該應用程序將在請求時提供服務,到達http://127.0.0.1:8080/

 1import cherrypy
 2
 3
 4class HelloWorld(object):
 5    @cherrypy.expose
 6    def index(self):
 7        return "Hello world!"
 8
 9
10if __name__ == '__main__':
11    cherrypy.quickstart(HelloWorld())

將此代碼段存儲到名為 tut01.py 執行如下:

$ python tut01.py

這將顯示以下內容:

 1[24/Feb/2014:21:01:46] ENGINE Listening for SIGHUP.
 2[24/Feb/2014:21:01:46] ENGINE Listening for SIGTERM.
 3[24/Feb/2014:21:01:46] ENGINE Listening for SIGUSR1.
 4[24/Feb/2014:21:01:46] ENGINE Bus STARTING
 5CherryPy Checker:
 6The Application mounted at '' has an empty config.
 7
 8[24/Feb/2014:21:01:46] ENGINE Started monitor thread 'Autoreloader'.
 9[24/Feb/2014:21:01:46] ENGINE Serving on http://127.0.0.1:8080
10[24/Feb/2014:21:01:46] ENGINE Bus STARTED

這告訴你一些事情。前三行表示服務器將處理 signal 為你。下一行告訴您服務器的當前狀態,因為此時服務器處于 STARTING 階段。然后,系統會通知您的應用程序沒有為其設置特定的配置。接下來,服務器啟動一些內部實用程序,稍后我們將對此進行解釋。最后,服務器表明它現在已經準備好接受傳入的通信,因為它正在監聽地址。 127.0.0.1:8080 .換句話說,在那個階段,您的應用程序就可以使用了。

在繼續之前,讓我們討論一下關于配置不足的消息。默認情況下,Cherrypy有一個特性,它將檢查配置應用程序時可以提供的設置的語法正確性。如果沒有提供,日志中將顯示警告消息。那根原木是無害的,不會阻止CherryPy工作。你可以參考 the documentation above 了解如何設置配置。

教程2:不同的URL導致不同的函數?

顯然,您的應用程序將處理多個URL。假設您有一個應用程序,每次調用它時都會生成一個隨機字符串:

 1import random
 2import string
 3
 4import cherrypy
 5
 6
 7class StringGenerator(object):
 8    @cherrypy.expose
 9    def index(self):
10        return "Hello world!"
11
12    @cherrypy.expose
13    def generate(self):
14        return ''.join(random.sample(string.hexdigits, 8))
15
16
17if __name__ == '__main__':
18    cherrypy.quickstart(StringGenerator())

將此保存到名為 tut02.py 運行如下:

$ python tut02.py

現在轉到http://localhost:8080/generate,您的瀏覽器將顯示一個隨機字符串。

讓我們花一分鐘時間來分解這里發生的事情。這是您在瀏覽器中鍵入的URL:http://localhost:8080/generate

此URL包含多個部分:

  • http:// 這大致表明它是一個使用HTTP協議的URL(請參見 RFC 2616

  • localhost:8080 是服務器的地址。它由主機名和端口組成。

  • /generate 它是URL的路徑段。這就是Cherrypy用來定位 exposed 響應的功能或方法。

在這里,Cherrypy使用 index() 處理方法 / 以及 generate() 處理方法 /generate

教程3:我的URL有參數?

在前面的教程中,我們已經了解了如何創建可以生成隨機字符串的應用程序?,F在假設您希望動態地指示該字符串的長度。

 1import random
 2import string
 3
 4import cherrypy
 5
 6
 7class StringGenerator(object):
 8    @cherrypy.expose
 9    def index(self):
10        return "Hello world!"
11
12    @cherrypy.expose
13    def generate(self, length=8):
14        return ''.join(random.sample(string.hexdigits, int(length)))
15
16
17if __name__ == '__main__':
18    cherrypy.quickstart(StringGenerator())

將此保存到名為 tut03.py 運行如下:

$ python tut03.py

現在轉到http://localhost:8080/generate?長度=16,瀏覽器將顯示長度為16的生成字符串。請注意,我們如何從Python的默認參數值中獲益,以支持URL,如http://localhost:8080/generate still。

在這樣的URL中,后面的部分 ? 稱為查詢字符串。傳統上,查詢字符串通過傳遞一組(鍵、值)對來將URL上下文化。這些對的格式是 key=value .每對被一個 & 性格。

注意我們必須如何轉換給定的 length 值為整數。實際上,值是作為字符串從客戶機發送到服務器的。

與cherrypy將URL路徑段映射到公開的函數很相似,查詢字符串鍵映射到公開的函數參數。

教程4:提交此表單?

Cherrypy是一個構建Web應用程序的Web框架。應用程序采用的最傳統的形式是通過與Cherrypy服務器對話的HTML用戶界面。

讓我們通過下面的示例來了解如何處理HTML表單。

 1import random
 2import string
 3
 4import cherrypy
 5
 6
 7class StringGenerator(object):
 8    @cherrypy.expose
 9    def index(self):
10        return """<html>
11          <head></head>
12          <body>
13            <form method="get" action="generate">
14              <input type="text" value="8" name="length" />
15              <button type="submit">Give it now!</button>
16            </form>
17          </body>
18        </html>"""
19
20    @cherrypy.expose
21    def generate(self, length=8):
22        return ''.join(random.sample(string.hexdigits, int(length)))
23
24
25if __name__ == '__main__':
26    cherrypy.quickstart(StringGenerator())

將此保存到名為 tut04.py 運行如下:

$ python tut04.py

現在轉到http://localhost:8080/和您的瀏覽器,這將顯示一個簡單的輸入字段,指示要生成的字符串的長度。

注意,在這個示例中,表單使用 GET 方法和當您按下 Give it now! 按鈕,表單使用與中相同的URL發送 previous 輔導的。HTML表單還支持 POST 方法,在這種情況下,查詢字符串不會附加到URL,而是作為客戶端請求的主體發送到服務器。但是,這不會更改應用程序的Exposed方法,因為CherryPy處理這兩種方法的方式相同,并且使用Exposed的處理程序參數來處理查詢字符串(鍵、值)對。

教程5:跟蹤我的最終用戶的活動?

應用程序需要跟蹤用戶的活動一段時間并不少見。通常的機制是使用 session identifier 這是在用戶和應用程序之間的對話中進行的。

 1import random
 2import string
 3
 4import cherrypy
 5
 6
 7class StringGenerator(object):
 8    @cherrypy.expose
 9    def index(self):
10        return """<html>
11          <head></head>
12          <body>
13            <form method="get" action="generate">
14              <input type="text" value="8" name="length" />
15              <button type="submit">Give it now!</button>
16            </form>
17          </body>
18        </html>"""
19
20    @cherrypy.expose
21    def generate(self, length=8):
22        some_string = ''.join(random.sample(string.hexdigits, int(length)))
23        cherrypy.session['mystring'] = some_string
24        return some_string
25
26    @cherrypy.expose
27    def display(self):
28        return cherrypy.session['mystring']
29
30
31if __name__ == '__main__':
32    conf = {
33        '/': {
34            'tools.sessions.on': True
35        }
36    }
37    cherrypy.quickstart(StringGenerator(), '/', conf)

將此保存到名為 tut05.py 運行如下:

$ python tut05.py

在本例中,我們生成的字符串與 previous 教程,但也存儲在當前會話中。如果您轉到http://localhost:8080/,生成一個隨機字符串,然后轉到http://localhost:8080/display,您將看到剛剛生成的字符串。

第30-34行向您展示了如何在Cherrypy應用程序中啟用會話支持。默認情況下,Cherrypy會將會話保存在進程的內存中。它支持更持久的 backends 也。

教程6:我的javascripts、css和images呢??

Web應用程序通常也由靜態內容組成,如javascript、CSS文件或圖像。Cherrypy提供了為最終用戶提供靜態內容的支持。

假設您希望將樣式表與應用程序關聯,以顯示藍色背景色(為什么不這樣做?).

首先,將以下樣式表保存到名為 style.css 并存儲到本地目錄中 public/css .

1body {
2  background-color: blue;
3}

現在,讓我們更新HTML代碼,以便使用http://localhost:8080/static/css/style.css URL鏈接到樣式表。

 1import os, os.path
 2import random
 3import string
 4
 5import cherrypy
 6
 7
 8class StringGenerator(object):
 9    @cherrypy.expose
10    def index(self):
11        return """<html>
12          <head>
13            <link href="/static/css/style.css" rel="stylesheet">
14          </head>
15          <body>
16            <form method="get" action="generate">
17              <input type="text" value="8" name="length" />
18              <button type="submit">Give it now!</button>
19            </form>
20          </body>
21        </html>"""
22
23    @cherrypy.expose
24    def generate(self, length=8):
25        some_string = ''.join(random.sample(string.hexdigits, int(length)))
26        cherrypy.session['mystring'] = some_string
27        return some_string
28
29    @cherrypy.expose
30    def display(self):
31        return cherrypy.session['mystring']
32
33
34if __name__ == '__main__':
35    conf = {
36        '/': {
37            'tools.sessions.on': True,
38            'tools.staticdir.root': os.path.abspath(os.getcwd())
39        },
40        '/static': {
41            'tools.staticdir.on': True,
42            'tools.staticdir.dir': './public'
43        }
44    }
45    cherrypy.quickstart(StringGenerator(), '/', conf)

將此保存到名為 tut06.py 運行如下:

$ python tut06.py

在http://localhost:8080/上,您應該看到一個鮮艷的藍色。

Cherrypy提供了服務于單個文件或完整目錄結構的支持。大多數情況下,這就是您將要做的,這就是上面的代碼所演示的。首先,我們指出 root 所有靜態內容的目錄。出于安全考慮,這必須是絕對路徑。如果在尋找與URL匹配的路徑時只提供相對路徑,Cherrypy會抱怨。

然后我們指出路徑段以之開頭的所有URL /static 將用作靜態內容。我們將該URL映射到 public 目錄,目錄的直接子目錄 root 目錄。的整個子樹 public 目錄將用作靜態內容。Cherrypy將把URL映射到該目錄中的路徑。這就是為什么 /static/css/style.css 發現于 public/css/style.css .

教程7:讓我們休息一下?

如今,Web應用程序公開某種數據模型或計算函數并不罕見。在不詳細說明的情況下,一個策略是遵循 REST principles edicted by Roy T. Fielding .

大致來說,它假定您可以標識一個資源,并且可以通過該標識符來標識該資源。

“為什么?”你可以問。好吧,大多數情況下,這些原則是為了確保您盡可能地將應用程序所公開的實體與操作或使用它們的方式分離開來。為了接受這一觀點,開發人員通常會設計一個Web API來公開 (URL, HTTP method, data, constraints) .

注解

您經常會聽到REST和Web API在一起。前者是提供后者的一種策略。本教程不會深入了解整個Web API概念,因為它是一個更具吸引力的主題,但您應該在網上閱讀更多關于它的內容。

我們來看一個非?;镜腤eb API的小例子,稍微遵循REST原則。

 1import random
 2import string
 3
 4import cherrypy
 5
 6
 7@cherrypy.expose
 8class StringGeneratorWebService(object):
 9
10    @cherrypy.tools.accept(media='text/plain')
11    def GET(self):
12        return cherrypy.session['mystring']
13
14    def POST(self, length=8):
15        some_string = ''.join(random.sample(string.hexdigits, int(length)))
16        cherrypy.session['mystring'] = some_string
17        return some_string
18
19    def PUT(self, another_string):
20        cherrypy.session['mystring'] = another_string
21
22    def DELETE(self):
23        cherrypy.session.pop('mystring', None)
24
25
26if __name__ == '__main__':
27    conf = {
28        '/': {
29            'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
30            'tools.sessions.on': True,
31            'tools.response_headers.on': True,
32            'tools.response_headers.headers': [('Content-Type', 'text/plain')],
33        }
34    }
35    cherrypy.quickstart(StringGeneratorWebService(), '/', conf)

將此保存到名為 tut07.py 運行如下:

$ python tut07.py

在我們看到它起作用之前,讓我們先解釋一些事情。直到現在,Cherrypy還在創建一個用于匹配URL的公開方法樹。對于我們的Web API,我們希望強調實際請求的HTTP方法所扮演的角色。因此,我們創建了以它們命名的方法,并且通過用 cherrypy.expose .

但是,我們必須從匹配URL的默認機制切換到了解整個HTTP方法shenanigan的方法。這就是我們在第27行創建的 MethodDispatcher 實例。

然后我們強制作出反應 content-type 成為 text/plain 我們最終保證 GET 請求將只對接受該請求的客戶端作出響應 content-type 通過擁有 Accept: text/plain 在他們的請求中設置了頭。但是,我們只為該HTTP方法執行此操作,因為它對其他方法沒有太大意義。

在本教程中,我們將使用一個python客戶機而不是您的瀏覽器,否則我們將無法真正嘗試我們的web API。

請安裝 requests 通過以下命令:

$ pip install requests

然后啟動python終端并嘗試以下命令:

 1>>> import requests
 2>>> s = requests.Session()
 3>>> r = s.get('http://127.0.0.1:8080/')
 4>>> r.status_code
 5500
 6>>> r = s.post('http://127.0.0.1:8080/')
 7>>> r.status_code, r.text
 8(200, u'04A92138')
 9>>> r = s.get('http://127.0.0.1:8080/')
10>>> r.status_code, r.text
11(200, u'04A92138')
12>>> r = s.get('http://127.0.0.1:8080/', headers={'Accept': 'application/json'})
13>>> r.status_code
14406
15>>> r = s.put('http://127.0.0.1:8080/', params={'another_string': 'hello'})
16>>> r = s.get('http://127.0.0.1:8080/')
17>>> r.status_code, r.text
18(200, u'hello')
19>>> r = s.delete('http://127.0.0.1:8080/')
20>>> r = s.get('http://127.0.0.1:8080/')
21>>> r.status_code
22500

第一個也是最后一個 500 響應源于這樣一個事實:在第一種情況下,我們還沒有通過 POST 在后一種情況下,在我們刪除它之后,它就不存在了。

第12-14行向您展示了當我們的客戶機以JSON格式請求生成的字符串時應用程序的反應。由于我們將Web API配置為僅支持純文本,因此它返回適當的 HTTP error code .

注解

我們使用 Session 界面 requests 因此,它負責在每個后續請求中攜帶存儲在請求cookie中的會話ID。那很方便。

重要

這些天都是關于RESTful URL的,不是嗎?

很可能您的URL將由動態部分組成,您將無法與頁面處理程序匹配。例如, /library/12/book/15 由于段 1215 將不與任何可調用的python匹配。

這可以很容易地解決問題,其中介紹了兩個方便的Cherrypy特性 advanced section .

教程8:使用Ajax使其更平滑?

近年來,Web應用程序已經不再采用“HTML表單+刷新整個頁面”的簡單模式。這個傳統的方案仍然非常有效,但是用戶已經習慣了不刷新整個頁面的Web應用程序。從廣義上講,Web應用程序帶有執行的客戶端代碼,可以與后端進行通信,而無需刷新整個頁面。

本教程這次將涉及更多的代碼。首先,讓我們看看位于 public/css/style.css .

1body {
2  background-color: blue;
3}
4
5#the-string {
6  display: none;
7}

我們將添加一個關于將顯示生成字符串的元素的簡單規則。默認情況下,我們不顯示它。將以下HTML代碼保存到名為 index.html .

 1<!DOCTYPE html>
 2<html>
 3  <head>
 4    <link href="/static/css/style.css" rel="stylesheet">
 5    <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
 6    <script type="text/javascript">
 7      $(document).ready(function() {
 8
 9        $("#generate-string").click(function(e) {
10          $.post("/generator", {"length": $("input[name='length']").val()})
11           .done(function(string) {
12            $("#the-string").show();
13            $("#the-string input").val(string);
14          });
15          e.preventDefault();
16        });
17
18        $("#replace-string").click(function(e) {
19          $.ajax({
20            type: "PUT",
21            url: "/generator",
22            data: {"another_string": $("#the-string input").val()}
23          })
24          .done(function() {
25            alert("Replaced!");
26          });
27          e.preventDefault();
28        });
29
30        $("#delete-string").click(function(e) {
31          $.ajax({
32            type: "DELETE",
33            url: "/generator"
34          })
35          .done(function() {
36            $("#the-string").hide();
37          });
38          e.preventDefault();
39        });
40
41      });
42    </script>
43  </head>
44  <body>
45    <input type="text" value="8" name="length"/>
46    <button id="generate-string">Give it now!</button>
47    <div id="the-string">
48      <input type="text" />
49      <button id="replace-string">Replace</button>
50      <button id="delete-string">Delete it</button>
51    </div>
52  </body>
53</html>

我們將使用 jQuery framework 出于簡單的考慮,您可以用您最喜歡的工具替換它。頁面由簡單的HTML元素組成,用于獲取用戶輸入并顯示生成的字符串。它還包含客戶端代碼,用于與實際執行艱苦工作的后端API進行對話。

最后,這里是應用程序的代碼,它為上面的HTML頁面提供服務,并響應生成字符串的請求。兩者都由同一應用服務器托管。

 1import os, os.path
 2import random
 3import string
 4
 5import cherrypy
 6
 7
 8class StringGenerator(object):
 9    @cherrypy.expose
10    def index(self):
11        return open('index.html')
12
13
14@cherrypy.expose
15class StringGeneratorWebService(object):
16
17    @cherrypy.tools.accept(media='text/plain')
18    def GET(self):
19        return cherrypy.session['mystring']
20
21    def POST(self, length=8):
22        some_string = ''.join(random.sample(string.hexdigits, int(length)))
23        cherrypy.session['mystring'] = some_string
24        return some_string
25
26    def PUT(self, another_string):
27        cherrypy.session['mystring'] = another_string
28
29    def DELETE(self):
30        cherrypy.session.pop('mystring', None)
31
32
33if __name__ == '__main__':
34    conf = {
35        '/': {
36            'tools.sessions.on': True,
37            'tools.staticdir.root': os.path.abspath(os.getcwd())
38        },
39        '/generator': {
40            'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
41            'tools.response_headers.on': True,
42            'tools.response_headers.headers': [('Content-Type', 'text/plain')],
43        },
44        '/static': {
45            'tools.staticdir.on': True,
46            'tools.staticdir.dir': './public'
47        }
48    }
49    webapp = StringGenerator()
50    webapp.generator = StringGeneratorWebService()
51    cherrypy.quickstart(webapp, '/', conf)

將此保存到名為 tut08.py 運行如下:

$ python tut08.py

轉到http://127.0.0.1:8080/并播放輸入和按鈕以生成、替換或刪除字符串。注意頁面沒有刷新,只是內容的一部分。

注意你的前端如何使用一個簡潔的Web服務API與后端進行對話。非HTML客戶機可以很容易地使用相同的API。

教程9:數據是我的全部生活?

直到現在,所有生成的字符串都保存在會話中,默認情況下,這些字符串存儲在進程內存中。但是,您可以在磁盤或分布式內存存儲中持久化會話,這不是長期保存數據的正確方法。有會話來識別您的用戶,并攜帶用戶所攜帶操作所需的少量數據。

要存儲、保持和查詢數據,您需要一個適當的數據庫服務器。在各種范例支持下,有許多可供選擇:

  • 關系型:PostgreSQL、SQLite、Mariadb、Firebird

  • 面向列:HBase、Cassandra

  • 密鑰存儲:redis,memcached

  • 面向文檔:CouchDB、MongoDB

  • 圖形導向:NEO4J

讓我們集中討論關系型的,因為它們是最常見的,并且可能是您首先想要學習的內容。

為了減少這些教程的依賴項數量,我們將 sqlite python直接支持的數據庫。

我們的應用程序將把會話中生成的字符串存儲替換為sqlite數據庫。應用程序的HTML代碼與 tutorial 08 .因此,讓我們只關注應用程序代碼本身:

 1import os, os.path
 2import random
 3import sqlite3
 4import string
 5import time
 6
 7import cherrypy
 8
 9DB_STRING = "my.db"
10
11
12class StringGenerator(object):
13    @cherrypy.expose
14    def index(self):
15        return open('index.html')
16
17
18@cherrypy.expose
19class StringGeneratorWebService(object):
20
21    @cherrypy.tools.accept(media='text/plain')
22    def GET(self):
23        with sqlite3.connect(DB_STRING) as c:
24            cherrypy.session['ts'] = time.time()
25            r = c.execute("SELECT value FROM user_string WHERE session_id=?",
26                          [cherrypy.session.id])
27            return r.fetchone()
28
29    def POST(self, length=8):
30        some_string = ''.join(random.sample(string.hexdigits, int(length)))
31        with sqlite3.connect(DB_STRING) as c:
32            cherrypy.session['ts'] = time.time()
33            c.execute("INSERT INTO user_string VALUES (?, ?)",
34                      [cherrypy.session.id, some_string])
35        return some_string
36
37    def PUT(self, another_string):
38        with sqlite3.connect(DB_STRING) as c:
39            cherrypy.session['ts'] = time.time()
40            c.execute("UPDATE user_string SET value=? WHERE session_id=?",
41                      [another_string, cherrypy.session.id])
42
43    def DELETE(self):
44        cherrypy.session.pop('ts', None)
45        with sqlite3.connect(DB_STRING) as c:
46            c.execute("DELETE FROM user_string WHERE session_id=?",
47                      [cherrypy.session.id])
48
49
50def setup_database():
51    """
52    Create the `user_string` table in the database
53    on server startup
54    """
55    with sqlite3.connect(DB_STRING) as con:
56        con.execute("CREATE TABLE user_string (session_id, value)")
57
58
59def cleanup_database():
60    """
61    Destroy the `user_string` table from the database
62    on server shutdown.
63    """
64    with sqlite3.connect(DB_STRING) as con:
65        con.execute("DROP TABLE user_string")
66
67
68if __name__ == '__main__':
69    conf = {
70        '/': {
71            'tools.sessions.on': True,
72            'tools.staticdir.root': os.path.abspath(os.getcwd())
73        },
74        '/generator': {
75            'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
76            'tools.response_headers.on': True,
77            'tools.response_headers.headers': [('Content-Type', 'text/plain')],
78        },
79        '/static': {
80            'tools.staticdir.on': True,
81            'tools.staticdir.dir': './public'
82        }
83    }
84
85    cherrypy.engine.subscribe('start', setup_database)
86    cherrypy.engine.subscribe('stop', cleanup_database)
87
88    webapp = StringGenerator()
89    webapp.generator = StringGeneratorWebService()
90    cherrypy.quickstart(webapp, '/', conf)

將此保存到名為 tut09.py 運行如下:

$ python tut09.py

讓我們先看看如何創建兩個函數來創建和銷毀數據庫中的表。這些函數在第85-86行注冊到Cherrypy的服務器,以便在服務器啟動和停止時調用它們。

接下來,注意我們如何用對數據庫的調用替換所有會話代碼。我們使用會話ID來標識數據庫中用戶的字符串。由于會議將在一段時間后結束,所以這可能不是正確的方法。更好的方法是將用戶的登錄名或更具彈性的唯一標識符關聯起來。為了演示,應該這樣做。

重要

在這個例子中,我們仍然必須將會話設置為一個虛擬值,這樣會話就不會 discarded 應Cherrypy的要求。因為我們現在使用數據庫來存儲生成的字符串,所以只需在會話中存儲一個虛擬的時間戳。

注解

不幸的是,python中的sqlite禁止我們在線程之間共享連接。由于Cherrypy是一個多線程服務器,這將是一個問題。這就是我們每次調用時打開和關閉與數據庫的連接的原因。這顯然不是真正的生產友好型,建議使用更強大的數據庫引擎或更高級別的庫,例如 SQLAlchemy ,以更好地支持應用程序的需求。

教程10:使用react.js使其成為一個現代的單頁應用程序?

近年來,客戶端單頁應用程序(SPA)逐漸吞噬了服務器端生成的內容Web應用程序的午餐。

本教程演示如何與 React.js 是Facebook于2013年發布的SPA的一個javascript庫。請參閱react.js文檔了解更多信息。

為了演示它,讓我們使用 tutorial 09 .但是,我們將替換HTML和JavaScript代碼。

首先,讓我們看看HTML代碼是如何改變的:

 1 <!DOCTYPE html>
 2 <html>
 3    <head>
 4      <link href="/static/css/style.css" rel="stylesheet">
 5      <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>
 6      <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
 7      <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
 8    </head>
 9    <body>
10      <div id="generator"></div>
11      <script type="text/babel" src="static/js/gen.js"></script>
12    </body>
13 </html>

基本上,我們已經刪除了使用jquery的整個javascript代碼。相反,我們加載react.js庫以及名為 gen.js 位于 public/js 目錄:

  1var StringGeneratorBox = React.createClass({
  2  handleGenerate: function() {
  3    var length = this.state.length;
  4    this.setState(function() {
  5      $.ajax({
  6        url: this.props.url,
  7        dataType: 'text',
  8        type: 'POST',
  9        data: {
 10          "length": length
 11        },
 12        success: function(data) {
 13          this.setState({
 14            length: length,
 15            string: data,
 16            mode: "edit"
 17          });
 18        }.bind(this),
 19        error: function(xhr, status, err) {
 20          console.error(this.props.url,
 21            status, err.toString()
 22          );
 23        }.bind(this)
 24      });
 25    });
 26  },
 27  handleEdit: function() {
 28    var new_string = this.state.string;
 29    this.setState(function() {
 30      $.ajax({
 31        url: this.props.url,
 32        type: 'PUT',
 33        data: {
 34          "another_string": new_string
 35        },
 36        success: function() {
 37          this.setState({
 38            length: new_string.length,
 39            string: new_string,
 40            mode: "edit"
 41          });
 42        }.bind(this),
 43        error: function(xhr, status, err) {
 44          console.error(this.props.url,
 45            status, err.toString()
 46          );
 47        }.bind(this)
 48      });
 49    });
 50  },
 51  handleDelete: function() {
 52    this.setState(function() {
 53      $.ajax({
 54        url: this.props.url,
 55        type: 'DELETE',
 56        success: function() {
 57          this.setState({
 58            length: "8",
 59            string: "",
 60            mode: "create"
 61          });
 62        }.bind(this),
 63        error: function(xhr, status, err) {
 64          console.error(this.props.url,
 65            status, err.toString()
 66          );
 67        }.bind(this)
 68      });
 69    });
 70  },
 71  handleLengthChange: function(length) {
 72    this.setState({
 73      length: length,
 74      string: "",
 75      mode: "create"
 76    });
 77  },
 78  handleStringChange: function(new_string) {
 79    this.setState({
 80      length: new_string.length,
 81      string: new_string,
 82      mode: "edit"
 83    });
 84  },
 85  getInitialState: function() {
 86    return {
 87      length: "8",
 88      string: "",
 89      mode: "create"
 90    };
 91  },
 92  render: function() {
 93    return (
 94      <div className="stringGenBox">
 95            <StringGeneratorForm onCreateString={this.handleGenerate}
 96                                 onReplaceString={this.handleEdit}
 97                                 onDeleteString={this.handleDelete}
 98                                 onLengthChange={this.handleLengthChange}
 99                                 onStringChange={this.handleStringChange}
100                                 mode={this.state.mode}
101                                 length={this.state.length}
102                                 string={this.state.string}/>
103      </div>
104    );
105  }
106});
107
108var StringGeneratorForm = React.createClass({
109  handleCreate: function(e) {
110    e.preventDefault();
111    this.props.onCreateString();
112  },
113  handleReplace: function(e) {
114    e.preventDefault();
115    this.props.onReplaceString();
116  },
117  handleDelete: function(e) {
118    e.preventDefault();
119    this.props.onDeleteString();
120  },
121  handleLengthChange: function(e) {
122    e.preventDefault();
123    var length = React.findDOMNode(this.refs.length).value.trim();
124    this.props.onLengthChange(length);
125  },
126  handleStringChange: function(e) {
127    e.preventDefault();
128    var string = React.findDOMNode(this.refs.string).value.trim();
129    this.props.onStringChange(string);
130  },
131  render: function() {
132    if (this.props.mode == "create") {
133      return (
134        <div>
135           <input  type="text" ref="length" defaultValue="8" value={this.props.length} onChange={this.handleLengthChange} />
136           <button onClick={this.handleCreate}>Give it now!</button>
137        </div>
138      );
139    } else if (this.props.mode == "edit") {
140      return (
141        <div>
142           <input type="text" ref="string" value={this.props.string} onChange={this.handleStringChange} />
143           <button onClick={this.handleReplace}>Replace</button>
144           <button onClick={this.handleDelete}>Delete it</button>
145        </div>
146      );
147    }
148
149    return null;
150  }
151});
152
153React.render(
154  <StringGeneratorBox url="/generator" />,
155  document.getElementById('generator')
156);

真的!這么簡單的東西需要多少代碼,不是嗎?入口點是最后幾行,我們在其中指示要呈現 StringGeneratorBox react.js類 generator 部門

當呈現頁面時,該組件也是如此。請注意,它也是如何由呈現表單本身的另一個組件構成的。

對于這樣一個簡單的例子來說,這可能有點過頭了,但希望能讓您在這個過程中開始使用react.js。

沒有什么好說的,希望代碼的含義相當清楚。組件具有內部 state 我們在其中存儲用戶生成/修改的當前字符串。

當用戶 changes the content of the input boxes ,狀態在客戶端更新。然后,單擊按鈕時,該狀態將使用API端點發送到后端服務器,并執行相應的操作。然后,狀態會更新,視圖也會更新。

教程11:組織我的代碼?

Cherrypy有一個強大的體系結構,可以幫助您以一種更容易維護和更靈活的方式組織代碼。

您可以使用幾種機制,本教程將重點介紹三種主要機制:

為了理解他們,讓我們想象一下你在一家超市:

  • 您有幾個till,每個till都有人排隊(這些是您的請求)

  • 你有不同的部分,包括食物和其他東西(這些是你的數據)

  • 最后,您將擁有超市人員及其日常任務,以確保各部分始終有序(這是您的后端)

盡管非常簡單,但這離應用程序的行為方式并不遠。Cherrypy幫助您以反映這些高級思想的方式構造應用程序。

調度員?

回到超級商店示例,您可能希望基于till執行操作:

  • 十件以下的籃子要收銀

  • 為殘疾人提供救濟

  • 孕婦有一張支票

  • 在只有商店卡才能使用的地方有一個收銀臺

為了支持這些用例,Cherrypy提供了一種稱為 dispatcher .調度器在請求處理過程的早期執行,以確定應用程序的哪段代碼將處理傳入的請求?;蛘?,為了繼續進行商店的類比,調度員將決定在引導客戶到達之前要選擇哪一個。

工具?

假設你的商店決定進行折扣狂潮,但只針對特定類別的客戶。Cherrypy將通過一種稱為 tool .

工具是基于每個請求運行的代碼,用于執行其他工作。通常,工具是一個簡單的python函數,在cherrypy的請求處理過程中在給定點執行。

插件?

正如我們所看到的,這家商店有一批致力于管理庫存和處理任何客戶期望的人員。

在Cherrypy的世界中,這就轉化為擁有在任何請求生命周期之外運行的函數。這些函數應該處理后臺任務、長壽命連接(例如到數據庫的連接)等。

Plugins 被這樣稱呼是因為他們和櫻桃一起工作 engine 并通過您的操作擴展它。

教程12:使用pytest和代碼覆蓋率?

比重試驗?

讓我們重溫一下 Tutorial 2 .

 1import random
 2import string
 3
 4import cherrypy
 5
 6
 7class StringGenerator(object):
 8    @cherrypy.expose
 9    def index(self):
10        return "Hello world!"
11
12    @cherrypy.expose
13    def generate(self):
14        return ''.join(random.sample(string.hexdigits, 8))
15
16
17if __name__ == '__main__':
18    cherrypy.quickstart(StringGenerator())

將此保存到名為 tut12.py .

現在生成測試文件:

 1import cherrypy
 2from cherrypy.test import helper
 3
 4from tut12 import StringGenerator
 5
 6class SimpleCPTest(helper.CPWebCase):
 7    @staticmethod
 8    def setup_server():
 9        cherrypy.tree.mount(StringGenerator(), '/', {})
10
11    def test_index(self):
12        self.getPage("/")
13        self.assertStatus('200 OK')
14    def test_generate(self):
15        self.getPage("/generate")
16        self.assertStatus('200 OK')

將此保存到名為 test_tut12.py 并運行

$ pytest -v test_tut12.py

注解

如果你沒有 pytest 安裝后,您需要在 pip install pytest

我們現在有了一個很好的方法來練習我們的應用程序生成測試。

添加代碼覆蓋率?

要獲得代碼覆蓋率,只需運行

$ pytest --cov=tut12 --cov-report term-missing test_tut12.py

注解

add coverage support to pytest ,您需要在之前安裝它 pip install pytest-cov

這說明有一行丟失了。當然,這是因為只有在直接啟動python程序時才執行。我們可以在 tut12.py

17if __name__ == '__main__':  # pragma: no cover
18    cherrypy.quickstart(StringGenerator())

當您重新運行代碼覆蓋率時,它現在應該顯示100%。

注解

在CI中使用時,您可能希望集成 Codecov , LandscapeCoveralls 在您的項目中存儲和跟蹤覆蓋率數據。