教程

本教程将指导您学习基本但完整的Cherrypy应用程序,这些应用程序将向您展示常见的概念以及稍微高级一些的概念。

教程1:基本的Web应用程序

下面的示例演示了可以用cherrypy编写的最基本的应用程序。它启动一个服务器并托管一个应用程序,该应用程序将在请求时提供服务,到达http://127.0.0.1:8080/

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import cherrypy


class HelloWorld(object):
    @cherrypy.expose
    def index(self):
        return "Hello world!"


if __name__ == '__main__':
    cherrypy.quickstart(HelloWorld())

将此代码段存储到名为 tut01.py 执行如下:

$ python tut01.py

这将显示以下内容:

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

[24/Feb/2014:21:01:46] ENGINE Started monitor thread 'Autoreloader'.
[24/Feb/2014:21:01:46] ENGINE Serving on http://127.0.0.1:8080
[24/Feb/2014:21:01:46] ENGINE Bus STARTED

这告诉你一些事情。前三行表示服务器将处理 signal 为你。下一行告诉您服务器的当前状态,因为此时服务器处于 STARTING 阶段。然后,系统会通知您的应用程序没有为其设置特定的配置。接下来,服务器启动一些内部实用程序,稍后我们将对此进行解释。最后,服务器表明它现在已经准备好接受传入的通信,因为它正在监听地址。 127.0.0.1:8080 .换句话说,在那个阶段,您的应用程序就可以使用了。

在继续之前,让我们讨论一下关于配置不足的消息。默认情况下,Cherrypy有一个特性,它将检查配置应用程序时可以提供的设置的语法正确性。如果没有提供,日志中将显示警告消息。那根原木是无害的,不会阻止CherryPy工作。你可以参考 the documentation above 了解如何设置配置。

教程2:不同的URL导致不同的函数

显然,您的应用程序将处理多个URL。假设您有一个应用程序,每次调用它时都会生成一个随机字符串:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import random
import string

import cherrypy


class StringGenerator(object):
    @cherrypy.expose
    def index(self):
        return "Hello world!"

    @cherrypy.expose
    def generate(self):
        return ''.join(random.sample(string.hexdigits, 8))


if __name__ == '__main__':
    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有参数

在前面的教程中,我们已经了解了如何创建可以生成随机字符串的应用程序。现在假设您希望动态地指示该字符串的长度。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import random
import string

import cherrypy


class StringGenerator(object):
    @cherrypy.expose
    def index(self):
        return "Hello world!"

    @cherrypy.expose
    def generate(self, length=8):
        return ''.join(random.sample(string.hexdigits, int(length)))


if __name__ == '__main__':
    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表单。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import random
import string

import cherrypy


class StringGenerator(object):
    @cherrypy.expose
    def index(self):
        return """<html>
          <head></head>
          <body>
            <form method="get" action="generate">
              <input type="text" value="8" name="length" />
              <button type="submit">Give it now!</button>
            </form>
          </body>
        </html>"""

    @cherrypy.expose
    def generate(self, length=8):
        return ''.join(random.sample(string.hexdigits, int(length)))


if __name__ == '__main__':
    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 这是在用户和应用程序之间的对话中进行的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import random
import string

import cherrypy


class StringGenerator(object):
    @cherrypy.expose
    def index(self):
        return """<html>
          <head></head>
          <body>
            <form method="get" action="generate">
              <input type="text" value="8" name="length" />
              <button type="submit">Give it now!</button>
            </form>
          </body>
        </html>"""

    @cherrypy.expose
    def generate(self, length=8):
        some_string = ''.join(random.sample(string.hexdigits, int(length)))
        cherrypy.session['mystring'] = some_string
        return some_string

    @cherrypy.expose
    def display(self):
        return cherrypy.session['mystring']


if __name__ == '__main__':
    conf = {
        '/': {
            'tools.sessions.on': True
        }
    }
    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 .

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

现在,让我们更新HTML代码,以便使用http://localhost:8080/static/css/style.css URL链接到样式表。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import os, os.path
import random
import string

import cherrypy


class StringGenerator(object):
    @cherrypy.expose
    def index(self):
        return """<html>
          <head>
            <link href="/static/css/style.css" rel="stylesheet">
          </head>
          <body>
            <form method="get" action="generate">
              <input type="text" value="8" name="length" />
              <button type="submit">Give it now!</button>
            </form>
          </body>
        </html>"""

    @cherrypy.expose
    def generate(self, length=8):
        some_string = ''.join(random.sample(string.hexdigits, int(length)))
        cherrypy.session['mystring'] = some_string
        return some_string

    @cherrypy.expose
    def display(self):
        return cherrypy.session['mystring']


if __name__ == '__main__':
    conf = {
        '/': {
            'tools.sessions.on': True,
            'tools.staticdir.root': os.path.abspath(os.getcwd())
        },
        '/static': {
            'tools.staticdir.on': True,
            'tools.staticdir.dir': './public'
        }
    }
    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概念,因为它是一个更具吸引力的主题,但您应该在网上阅读更多关于它的内容。

我们来看一个非常基本的Web API的小例子,稍微遵循REST原则。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import random
import string

import cherrypy


@cherrypy.expose
class StringGeneratorWebService(object):

    @cherrypy.tools.accept(media='text/plain')
    def GET(self):
        return cherrypy.session['mystring']

    def POST(self, length=8):
        some_string = ''.join(random.sample(string.hexdigits, int(length)))
        cherrypy.session['mystring'] = some_string
        return some_string

    def PUT(self, another_string):
        cherrypy.session['mystring'] = another_string

    def DELETE(self):
        cherrypy.session.pop('mystring', None)


if __name__ == '__main__':
    conf = {
        '/': {
            'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
            'tools.sessions.on': True,
            'tools.response_headers.on': True,
            'tools.response_headers.headers': [('Content-Type', 'text/plain')],
        }
    }
    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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
>>> import requests
>>> s = requests.Session()
>>> r = s.get('http://127.0.0.1:8080/')
>>> r.status_code
500
>>> r = s.post('http://127.0.0.1:8080/')
>>> r.status_code, r.text
(200, u'04A92138')
>>> r = s.get('http://127.0.0.1:8080/')
>>> r.status_code, r.text
(200, u'04A92138')
>>> r = s.get('http://127.0.0.1:8080/', headers={'Accept': 'application/json'})
>>> r.status_code
406
>>> r = s.put('http://127.0.0.1:8080/', params={'another_string': 'hello'})
>>> r = s.get('http://127.0.0.1:8080/')
>>> r.status_code, r.text
(200, u'hello')
>>> r = s.delete('http://127.0.0.1:8080/')
>>> r = s.get('http://127.0.0.1:8080/')
>>> r.status_code
500

第一个也是最后一个 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 .

1
2
3
4
5
6
7
body {
  background-color: blue;
}

#the-string {
  display: none;
}

我们将添加一个关于将显示生成字符串的元素的简单规则。默认情况下,我们不显示它。将以下HTML代码保存到名为 index.html .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html>
  <head>
    <link href="/static/css/style.css" rel="stylesheet">
    <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
    <script type="text/javascript">
      $(document).ready(function() {

        $("#generate-string").click(function(e) {
          $.post("/generator", {"length": $("input[name='length']").val()})
           .done(function(string) {
            $("#the-string").show();
            $("#the-string input").val(string);
          });
          e.preventDefault();
        });

        $("#replace-string").click(function(e) {
          $.ajax({
            type: "PUT",
            url: "/generator",
            data: {"another_string": $("#the-string input").val()}
          })
          .done(function() {
            alert("Replaced!");
          });
          e.preventDefault();
        });

        $("#delete-string").click(function(e) {
          $.ajax({
            type: "DELETE",
            url: "/generator"
          })
          .done(function() {
            $("#the-string").hide();
          });
          e.preventDefault();
        });

      });
    </script>
  </head>
  <body>
    <input type="text" value="8" name="length"/>
    <button id="generate-string">Give it now!</button>
    <div id="the-string">
      <input type="text" />
      <button id="replace-string">Replace</button>
      <button id="delete-string">Delete it</button>
    </div>
  </body>
</html>

我们将使用 jQuery framework 出于简单的考虑,您可以用您最喜欢的工具替换它。页面由简单的HTML元素组成,用于获取用户输入并显示生成的字符串。它还包含客户端代码,用于与实际执行艰苦工作的后端API进行对话。

最后,这里是应用程序的代码,它为上面的HTML页面提供服务,并响应生成字符串的请求。两者都由同一应用服务器托管。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import os, os.path
import random
import string

import cherrypy


class StringGenerator(object):
    @cherrypy.expose
    def index(self):
        return open('index.html')


@cherrypy.expose
class StringGeneratorWebService(object):

    @cherrypy.tools.accept(media='text/plain')
    def GET(self):
        return cherrypy.session['mystring']

    def POST(self, length=8):
        some_string = ''.join(random.sample(string.hexdigits, int(length)))
        cherrypy.session['mystring'] = some_string
        return some_string

    def PUT(self, another_string):
        cherrypy.session['mystring'] = another_string

    def DELETE(self):
        cherrypy.session.pop('mystring', None)


if __name__ == '__main__':
    conf = {
        '/': {
            'tools.sessions.on': True,
            'tools.staticdir.root': os.path.abspath(os.getcwd())
        },
        '/generator': {
            'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
            'tools.response_headers.on': True,
            'tools.response_headers.headers': [('Content-Type', 'text/plain')],
        },
        '/static': {
            'tools.staticdir.on': True,
            'tools.staticdir.dir': './public'
        }
    }
    webapp = StringGenerator()
    webapp.generator = StringGeneratorWebService()
    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 .因此,让我们只关注应用程序代码本身:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import os, os.path
import random
import sqlite3
import string
import time

import cherrypy

DB_STRING = "my.db"


class StringGenerator(object):
    @cherrypy.expose
    def index(self):
        return open('index.html')


@cherrypy.expose
class StringGeneratorWebService(object):

    @cherrypy.tools.accept(media='text/plain')
    def GET(self):
        with sqlite3.connect(DB_STRING) as c:
            cherrypy.session['ts'] = time.time()
            r = c.execute("SELECT value FROM user_string WHERE session_id=?",
                          [cherrypy.session.id])
            return r.fetchone()

    def POST(self, length=8):
        some_string = ''.join(random.sample(string.hexdigits, int(length)))
        with sqlite3.connect(DB_STRING) as c:
            cherrypy.session['ts'] = time.time()
            c.execute("INSERT INTO user_string VALUES (?, ?)",
                      [cherrypy.session.id, some_string])
        return some_string

    def PUT(self, another_string):
        with sqlite3.connect(DB_STRING) as c:
            cherrypy.session['ts'] = time.time()
            c.execute("UPDATE user_string SET value=? WHERE session_id=?",
                      [another_string, cherrypy.session.id])

    def DELETE(self):
        cherrypy.session.pop('ts', None)
        with sqlite3.connect(DB_STRING) as c:
            c.execute("DELETE FROM user_string WHERE session_id=?",
                      [cherrypy.session.id])


def setup_database():
    """
    Create the `user_string` table in the database
    on server startup
    """
    with sqlite3.connect(DB_STRING) as con:
        con.execute("CREATE TABLE user_string (session_id, value)")


def cleanup_database():
    """
    Destroy the `user_string` table from the database
    on server shutdown.
    """
    with sqlite3.connect(DB_STRING) as con:
        con.execute("DROP TABLE user_string")


if __name__ == '__main__':
    conf = {
        '/': {
            'tools.sessions.on': True,
            'tools.staticdir.root': os.path.abspath(os.getcwd())
        },
        '/generator': {
            'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
            'tools.response_headers.on': True,
            'tools.response_headers.headers': [('Content-Type', 'text/plain')],
        },
        '/static': {
            'tools.staticdir.on': True,
            'tools.staticdir.dir': './public'
        }
    }

    cherrypy.engine.subscribe('start', setup_database)
    cherrypy.engine.subscribe('stop', cleanup_database)

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

基本上,我们已经删除了使用jquery的整个javascript代码。相反,我们加载react.js库以及名为 gen.js 位于 public/js 目录:

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

var StringGeneratorForm = React.createClass({
  handleCreate: function(e) {
    e.preventDefault();
    this.props.onCreateString();
  },
  handleReplace: function(e) {
    e.preventDefault();
    this.props.onReplaceString();
  },
  handleDelete: function(e) {
    e.preventDefault();
    this.props.onDeleteString();
  },
  handleLengthChange: function(e) {
    e.preventDefault();
    var length = React.findDOMNode(this.refs.length).value.trim();
    this.props.onLengthChange(length);
  },
  handleStringChange: function(e) {
    e.preventDefault();
    var string = React.findDOMNode(this.refs.string).value.trim();
    this.props.onStringChange(string);
  },
  render: function() {
    if (this.props.mode == "create") {
      return (
        <div>
           <input  type="text" ref="length" defaultValue="8" value={this.props.length} onChange={this.handleLengthChange} />
           <button onClick={this.handleCreate}>Give it now!</button>
        </div>
      );
    } else if (this.props.mode == "edit") {
      return (
        <div>
           <input type="text" ref="string" value={this.props.string} onChange={this.handleStringChange} />
           <button onClick={this.handleReplace}>Replace</button>
           <button onClick={this.handleDelete}>Delete it</button>
        </div>
      );
    }

    return null;
  }
});

React.render(
  <StringGeneratorBox url="/generator" />,
  document.getElementById('generator')
);

真的!这么简单的东西需要多少代码,不是吗?入口点是最后几行,我们在其中指示要呈现 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 .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import random
import string

import cherrypy


class StringGenerator(object):
    @cherrypy.expose
    def index(self):
        return "Hello world!"

    @cherrypy.expose
    def generate(self):
        return ''.join(random.sample(string.hexdigits, 8))


if __name__ == '__main__':
    cherrypy.quickstart(StringGenerator())

将此保存到名为 tut12.py .

现在生成测试文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import cherrypy
from cherrypy.test import helper

from tut12 import StringGenerator

class SimpleCPTest(helper.CPWebCase):
    @staticmethod
    def setup_server():
        cherrypy.tree.mount(StringGenerator(), '/', {})

    def test_index(self):
        self.getPage("/")
        self.assertStatus('200 OK')
    def test_generate(self):
        self.getPage("/generate")
        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

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

当您重新运行代码覆盖率时,它现在应该显示100%。

注解

在CI中使用时,您可能希望集成 CodecovLandscapeCoveralls 在您的项目中存储和跟踪覆盖率数据。