如何用Python和CockroachDB构建一个完整的网络应用程序

320 阅读19分钟

在这篇文章中,我们要构建一个全栈式的网络应用程序,模拟一个游戏排行榜。我们的想法是让它尽可能的简单--一个页面显示排行榜列表,另一个页面添加新的排行榜条目。

作为一个Python开发者,你可能很熟悉这种语言的简单性。你可以从各种Web开发框架中选择,但如果你的目标是一个不复杂的Python Web应用程序,那么Flask就是你的朋友

Flask是一个轻量级的Web应用微框架,使你能够以最少的代码和配置快速启动和运行你的应用。但不要让这种简单性欺骗你:许多大公司使用Flask开发他们的企业网站,所以请放心,你可以根据需要通过单元测试和REST APIs逐步扩展你的应用程序的功能。

Flask网络框架在你的应用程序的后端工作。它依靠Jinja2作为前端的丰富模板引擎,在用户请求网页时渲染HTML。

在本教程中,我们设置了一个CockroachDB无服务器账户,将我们的CockroachDB数据库发布到云端。然而,我们并没有从Python中直接访问它。相反,我们使用SQLAlchemy,一个开源的Python语言的对象关系映射器。这个ORM使我们能够使用面向对象的范式与数据库数据进行通信、操作和查询。SQLAlchemy使用psycopg2数据库驱动(与我们用来访问PostgreSQL相同)来访问CockroachDB数据库。

最后,我们在Heroku使用一个免费账户进行注册。我们将在Heroku网络服务器上部署我们的网络应用,在那里,一切都将和在本地机器上的工作完全一样。

让我们潜入其中,亲身探索如何创建我们的排行榜网络应用。如果你想预览一下我们最终的结果,你可以在这个GitHub仓库里找到完整的应用代码。

创建一个最小的Flask应用程序

你可能已经熟悉Flask的功能了。让我们把它安装在我们的开发机器上,让一个简单的Flask应用程序运行。

第一步是使用pip(Python的软件包管理器)来安装Flask。

pip install flask

在这个项目中,我们试图以最小的努力实现一个友好的外观和感觉的网络应用。所以,我们使用Bootstrap,这是一个受人青睐的开源、响应式的CSS框架,旨在用于前端Web开发。

我们可以使用Flask-Bootstrap,用pip软件包管理器将Bootstrap依赖性安装到Flask应用程序中。

pip install flask_bootstrap

为了安装SQLAlchemy以及CockroachDB Python包(该包在CockroachDB和PostgreSQL之间有一些区别),我们运行下面的多合一命令。

pip install sqlalchemy sqlalchemy-cockroachdb psycopg2

在项目根目录下,我们创建一个新的app.py文件,作为我们Python应用程序的入口。

WEB_APP

+-- app.py

然后,我们打开app.py 文件并包含以下代码。

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello_world():
   return "<p>Hello, World!</p>"

在第一行,我们导入Flask类,它将实例化我们的应用程序。路由装饰器定义了端点URL,它将执行hello_world 函数,在用户的浏览器中返回问候信息。注意,我们返回的是一个纯文本字符串,没有提到它是HTML格式的,这很好,因为Flask默认将浏览器中的内容渲染成HTML。

要启动开发服务器,我们可以使用flask run CLI命令。然而,这个命令要求我们首先定义FLASK_APP 环境变量,以便将该命令指向我们的应用程序。要设置FLASK_APP 这个变量,我们可以使用下面的一个替代方法。

  • 在Linux中使用Bash,或在macOS中使用终端来设置FLASK_APP 变量。

export FLASK_APP=app

  • 在Windows中,我们可以通过CMD设置FLASK_APP

set FLASK_APP=app

  • 同样在Windows中,我们可以通过PowerShell设置FLASK_APP

$env:FLASK_APP = "app"

现在,我们用flask run CLI命令运行我们的Flask网络应用。

flask run

* Serving Flask app 'app' (lazy loading)
* Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.   
    Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Creating a Minimal Flask Application

使用Flask模板创建页面

我们已经有了一个在Flask上运行的最小的Web应用,所以让我们来构建和渲染排行榜和参赛表格的网页。

在后端代码中直接用Python构建HTML页面会很麻烦。所以,我们必须把任何HTML代码移到一个专门的文件中。更好的办法是,我们可以为这两个不同的网页建立两个HTML文件,然后让它们继承自同一个基础HTML页面。

首先,让我们创建一个结构来容纳Flask网络应用的HTML模板。

WEB_APP

+-- templates
|-- base.html
|-- index.html
  +-- player.html

但是Flask不知道如何渲染HTML。相反,它将这项任务委托给Jinja2,即Flask应用的默认模板引擎。当用户在前端请求网页时,Jinja2会渲染HTML。

现在,我们打开base.html 文件,包括以下标记代码,其中有Jinja2模板引擎为Python使用的块。

{%- extends "bootstrap/base.html" %}

{% block title %}Web App with Python and CockroachDB{% endblock %}

{% import "bootstrap/fixes.html" as fixes %}

{% block head %}
{{super()}}

{{fixes.ie8()}}
{%- endblock %}

{% block styles -%}
  {{super()}}  {# do not forget to call super or Bootstrap's own stylesheets will disappear! #}
  <link rel="stylesheet"   href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"   integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
{% endblock %}

注意,上面的代码不是你通常期望看到的HTML内容。base.html 文件中的代码使用了{% %}块,它允许Jinja2模板引擎确定基础页面中的逻辑和视觉部分。

此外,base.html 本身继承自 bootstrap/base.html 页面,所以与 Bootstrap 或其他前端框架相关的层叠样式表(CSS)或 JavaScript 依赖性应该放在base.html 文件中。

现在,我们打开index.html 文件,包括以下代码。

{%- extends "base.html" %}


{% import "bootstrap/utils.html" as utils %}


{% block content %}


<div class="row mt-4">
  <div class="col col-md-6 offset-md-3 alert alert-primary bg-danger text-white text-center">
    <span class="h1">GAME LEADERBOARD</span>
  </div>
</div>



<div class="row">
  <div class="col col-md-6 offset-md-3">
    <div class="row alert alert-warning bg-dark text-white">
        <span class="col col-1 mt-0 h1 text-center">#</span>
        <span class="col col-7 mt-0 h1 border-left border-light">Player</span>
        <span class="col col-4 mt-0 h1 border-left border-light text-right">Points</span>
      </div>
      {% for score in scores %}
      <div class="row alert alert-warning">
        <input type="hidden" value={{ score.id }}></input>
        <span class="col col-1 mt-0 h1 text-center">{{ score.ranking }}</span>
        <span class="col col-7 mt-0 border-left border-dark h1 text-truncate">{{ score.avatar }}&nbsp;{{ score.playername }}</span>
        <span class="col col-4 mt-0 border-left border-dark h1 text-right">{{ score.points }}</span>
    </div>
    {% endfor %}
  </div>
</div>



<div class="row">
  <div class="col col-md-3 offset-md-6">
    <input type="button" class="h1 mt-0 float-right text-center"
    value="➕ Add New Entry" onclick="location.href='/player'"/>
  </div>
</div>
{%- endblock %}

正如你所看到的,index.html 文件的第一行声明它继承了base.html 。 这种方法使我们的网页尽可能简单,并使我们网站的各个派生页面在视觉和功能上保持一致。

另外,请注意,index.html 代码使用大量的Bootstrap类,在一个"无表 "网格中展示了排行榜页面的行和列。

另一个有趣的地方是{% for score in scores %} Jinja2块,它允许我们的Python应用程序迭代分数集合并渲染每一行排行榜。在该块中,Flask渲染了玩家的排名、头像、名字和积分。

第二个页面是用户键入要显示在排行榜上的玩家数据的地方。要建立该页面,请打开player.html 文件并包含以下代码。

{%- extends "base.html" %}


{% import "bootstrap/utils.html" as utils %}


{% block content %}


<div class="row mt-4">
  <div class="col col-md-6 offset-md-3 alert alert-primary bg-danger text-white text-center">
    <span class="h1">Add New Entry</span>
  </div>
</div>



<form method="POST">
  <div class="row">
    <div class="col col-md-6 offset-md-3">
      <div class="row alert alert-warning bg-dark text-white">
        <span class="col col-md-3 mt-0 h1">Avatar</span>
        <span class="col col-md-6 mt-0 h1 border-left">Player Name</span>
        <span class="col col-md-3 mt-0 h1 border-left text-right">Points</span>
      </div>
      <div class="row">
        <input type="hidden" name="id" value={{ score.id }}></input>
        <select name="avatar" id="avatars" class="col col-md-3 mt-0 h1">
          {% for a in avatars %}
          <option value="{{ a }}" 
          {% if a == score.avatar %}
          selected
          {% endif %}
>{{ avatars\[a] }}</option>
          {% endfor %}
        </select>
        <input type="text" class="col col-md-6 mt-0 h1" name="playername" value={{ score.playername }}></input>
        <input type="number" class="col col-md-3 mt-0 h1 text-right" name="points" value={{ score.points }}></input>
      </div>
    </div>
  </div>
  <div class="row mt-0">
    <div class="col col-md-4 offset-md-5">
      <input type="submit" class="h1 mt-0 float-right text-center" value="✔️ Confirm"/>
      <input type="button" class="h1 mt-0 mr-4 button float-right text-center"
        value="❌ Cancel" onclick="location.href='/'"/>
    </div>
  </div>
</form>
  

  {%- endblock %}

我们在player.html代码中应用了与这里的index.html文件相同的无表格设计,但主要区别是我们使用了一个<form> 标签和输入HTML元素以及一个提交按钮。这个表单使用户能够输入和提交播放器的数据。

我们仍然必须为Jjinja2模板引擎提供数据来渲染。因此,我们为每个渲染的实体创建一个Python类。让我们制作models.py ,以容纳代表每个排行榜行的Score 类。

WEB_APP

+-- models.py


class Score():
    def __init__(self, id, avatar, playername, points):
      self.id = id
      self.avatar = avatar
      self.playername = playername
      self.points = points

接下来,让我们实现一个后端类来为视图提供数据。我们仍然没有数据库连接,但我们可以创建一些假的数据来测试我们的Web应用路线和工作流程,看看数据是否按预期呈现。

我们在根文件夹下创建一个新的leaderboard.py 文件,代码如下。

WEB_APP

+-- leaderboard.py


from models import Score


class Leaderboard:
    def get_scores(self):
        return self.prepare_scores()


    def prepare_scores(self):
        scores = []
        scores.append(Score(1, 8, "Marlene F. Martell", 1298))
        scores.append(Score(2, 1, "Curtis D. Torres", 800))
        scores.append(Score(3, 7, "Sandy D. Martinez", 765))
        scores.append(Score(4, 3, "James O. Ewing", 721))

        scores.sort(reverse=True, key=lambda e: e.points)

        result = list(map(lambda score, i: {'id': score.id,
                                            'ranking': i + 1,
                                            'avatar': self.get_avatar_dic()\[str(score.avatar)],
                                            'playername': score.playername,
                                            'points': score.points
                                            },
                        scores,
                        list(range(len(scores)))))

        return result

    def get_avatar_dic(self):
        return {
        "0":"not set",
        "1":"👨🏻",
        "2":"👨🏼",
        "3":"👨🏽",
        "4":"👨🏾",
        "5":"👨🏿",
        "6":"👩🏻",
        "7":"👩🏼",
        "8":"👩🏽",
        "9":"👩🏾",
        "10":"👩🏿"
        }

为了消费数据并将其传递给视图,让我们修改app.py 文件以创建Leaderboard类的实例并获得分数。

from flask import Flask, render_template, url_for
from flask_bootstrap import Bootstrap
from leaderboard import Leaderboard
from models import Score


app = Flask(__name__)
Bootstrap(app)


leaderboard = Leaderboard()


@app.route("/")
def index():



    scores = leaderboard.get_scores()
    return render_template("index.html",
                            scores=scores)



@app.route("/player", methods=["GET", "POST"])
def player():
    avatars = leaderboard.get_avatar_dic()
    score = Score(0, 0, "", 0)


    return render_template("player.html", score = score, avatars = avatars)

最后,我们重新运行应用程序以检查结果。

flask run

Creating Pages with Flask Templates

我们点击添加新条目,测试是否显示第二页来收集新玩家的数据。

Creating Pages with Flask Templates

我们已经成功创建了一个收集和显示球员分数的应用程序。

设置CockroachDB无服务器账户

我们仍然需要用实际数据库中的排行榜信息来替换假数据。按照下面的步骤来设置一个新的CockroachDB数据库集群。

  1. 如果你还没有,请注册一个CockroachDB无服务器账户
  2. 登录到你的CockroachDB Serverless账户。
  3. 在集群页面上,点击创建集群。
  4. 在创建集群页面上,选择CockroachDB Serverless。
  5. 点击创建你的免费集群。

注意,一旦你完成了账户注册并创建了一个新的集群,CockroachDB就会向你展示一个像下面这样的连接字符串。

CockroachDB Serverless Account

注意,CockroachDB只提供一次你的密码,所以在点击REVEAL_PASSWORD后要记下它。复制代码并将其保存在一个安全的地方。

还要注意的是,上面的连接字符串有一些属性,会根据你的特定集群而有所不同。请看如何在你的连接字符串中识别它们。

| Sample connection string | "postgresql://johndoe:xpto123abc999@free-tier7.aws-eu-west-1.cockroachlabs.cloud:26257/defaultdb?sslmode=require&options=--cluster%3Dmild-cat-1092" |   |   |   |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|---|---|---|
| <<YOUR-USER>>            | johndoe                                                                                                                                             |   |   |   |
| <<PASSWORD>>             | xpto123abc999                                                                                                                                       |   |   |   |
| <<TIER-NAME>>            | free-tier7                                                                                                                                          |   |   |   |
| <<CLOUD-REGION>>         | aws-eu-west-1                                                                                                                                       |   |   |   |
| <<CLUSTER-NAME>>         | mild-cat                                                                                                                                            |   |   |   |
| <<CLUSTER-NUMBER>>       | 1092      

由于我们使用占位符而不是这些属性的实际值,记得用你的配置相应地替换它们。

让我们创建一个.env 文件,将这些数据存储在一个环境变量中。

WEB_APP

+-- .env

DB_URI = "cockroachdb://<<YOUR-USER>>:<<PASSWORD>>@<<TIER-NAME>>.<<CLOUD-REGION>>.cockroachlabs.cloud:26257/game?sslmode=require&options=--cluster%3D<<CLUSTER-NAME>>-<<CLUSTER-NUMBER>>"

为了读取我们刚刚在.env 文件中添加到环境变量中的DB_URI 键值对,请安装python-dotenv 包。

pip install python-dotenv

初始化CockroachDB的排行榜表

让我们来定义代表每个排行榜项目的分数表的结构。请注意,ID是一个作为主键的通用唯一标识符(UUID),而头像是一个从1到10的数字,每个都代表一个表情符号的脸。这里是分数表模式的图解。

Initializing the CockroachDB Leaderboard Table

创建表最常见的方法之一是运行一个SQL脚本。幸运的是,在CockroachDB中运行SQL脚本与在PostgreSQL中运行SQL脚本没有什么不同,所以如果你熟悉其中一个,你就会很容易使用另一个。

让我们添加一个名为dbinit.sql 的新文件,用分数表创建和初始化我们的数据库,并用一些球员数据填充它。

WEB_APP

+-- dbinit.sql
SET sql_safe_updates = FALSE;

USE defaultdb;
DROP DATABASE IF EXISTS game CASCADE;
CREATE DATABASE IF NOT EXISTS game;

USE game;

CREATE TABLE scores (
    id uuid PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(),
    avatar INT8,
    playername STRING,
    points INT8,
    UNIQUE INDEX scores_playername (playername ASC)
);

INSERT INTO scores (avatar, playername, points)
  VALUES (8, 'Marlene F. Martell', 1298);

INSERT INTO scores (avatar, playername, points)
  VALUES (1, 'Curtis D. Torres', 800);

INSERT INTO scores (avatar, playername, points)
  VALUES (7, 'Sandy D. Martinez', 765);

INSERT INTO scores (avatar, playername, points)
  VALUES (3, 'James O. Ewing', 721);

让我们安装CockroachDB客户端界面,它允许用户运行SQL命令和浏览CockroachDB集群对象。

pip install cockroachdb

现在我们必须运行一个命令来读取dbinit.sql ,并将其传递给CockroachDB客户端。在那里,CockroachDB可以在CockroachDB Serverless连接到我们的集群,创建数据库,创建表,并将初始数据填充到表中。

cat dbinit.sql | cockroach sql --url "postgresql://<<YOUR-USER>>-<<PASSWORD>>@<<TIER-NAME>>.<<CLOUD-REGION>>.cockroachlabs.cloud:26257/defaultdb?sslmode=require&options=--cluster%3D<<CLUSTER-NAME>-<<CLUSTER-NUMBER>>"
SET
Time: 149.5532ms
SET
Time: 160.0248ms
DROP DATABASE
Time: 155.4707ms
CREATE DATABASE
Time: 177.0301ms
SET
Time: 158.1115ms
CREATE TABLE
Time: 168.1392ms
INSERT 1
Time: 172.3194ms
INSERT 1
Time: 155.4479ms
INSERT 1
Time: 155.9486ms
INSERT 1
Time: 155.4596ms

注意,cat 命令读取了dbinit.sql的内容,然后cockroach sql 命令使用连接字符串在云数据库上运行脚本。

为了确认我们的数据库结构和数据已经创建,我们在CockroachDB无服务器集群上连接到游戏数据库。

cockroach sql --url "postgresql://<<YOUR-USER>>-<<PASSWORD>>@<<TIER-NAME>>.<<CLOUD-REGION>>.cockroachlabs.cloud:26257/game?sslmode=require&options=--cluster%3D<<CLUSTER-NAME>-<<CLUSTER-NUMBER>>"
#
# Welcome to the CockroachDB SQL shell.
# All statements must be terminated by a semicolon.
# To exit, type: \q.
#
# Client version: CockroachDB CCL v20.1.17 (x86_64-w64-mingw32, built 2021/05/17 16:42:38, go1.13.9)
# Server version: CockroachDB CCL v21.1.7 (x86_64-unknown-linux-gnu, built 2021/08/09 
17:55:28, go1.15.14)
# Cluster ID: c0854300-2c35-44b4-a7d1-2af71acd3e4c
#
# Enter ? for a brief introduction.
#

这个对话框提示我们输入命令来查询我们的数据库。让我们运行SHOW DATABASES; 语句来列出集群内的数据库。

marcelooliveira@******.cockroachlabs.cloud:26257/game>

/game> SHOW DATABASES;

  database_name |      owner      | primary_region | regions | survival_goal
----------------+-----------------+----------------+---------+----------------        
  defaultdb     | root            | NULL           | {}      | NULL
  game          | marcelooliveira | NULL           | {}      | NULL
  postgres      | root            | NULL           | {}      | NULL
  system        | node            | NULL           | {}      | NULL

(4 rows)

现在,我们执行SHOW TABLES; 命令。

/game> SHOW TABLES;

  schema_name | table_name | type  |      owner      | estimated_row_count | locality
--------------+------------+-------+-----------------+---------------------+-----------
  public      | scores     | table | marcelooliveira |                   4 | NULL     

(1 row)

最后,我们使用SQL语法来查询游戏数据库中的分数表。注意,你可以像使用任何关系型数据库一样发出SQL查询。

/game> SELECT * FROM scores;

                   id                  | avatar |     playername     | points
---------------------------------------+--------+--------------------+---------       
  5d462286-fd12-4b82-a2c9-72c50f1bed29 |      7 | Sandy D. Martinez  |    765
  9781bf36-f651-4b19-a51a-47a165969a91 |      8 | Marlene F. Martell |   1298
  b5ddbc46-20ef-4742-80cf-59f451319994 |      1 | Curtis D. Torres   |    800
  c4550926-1cb5-488b-a935-6daa66b9e016 |      3 | James O. Ewing     |    721

(4 rows)

当你使用完CockroachDB SQL后,键入CTRL+C ,关闭当前连接,退出交互式SQL shell,回到终端。

/game> \[TYPE CTRL+C]

PS C:....\MY-PROJ> 

设置SQLAlchemy ORM以与CockroachDB协同工作

还记得我们在models.py 文件中为Score 模型设置的类吗?我们现在修改该文件,使其能够映射游戏数据库中分数表的确切结构。

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql.sqltypes import INT

Base = declarative_base()

class Score(Base):
    __tablename__ = 'scores'
    id = Column(UUID, primary_key=True)
    avatar = Column(INT)
    playername = Column(String)
    points = Column(INT)

注意,Score 类现在继承了SQLAlchemy包提供的Base 类。因此,SQLAlchemy可以跟踪内存中对象的变化,随后将其转化为SQL命令,针对CockroachDB数据库执行。

现在,我们创建一个新的transactions.py 文件,在这里我们将放置访问数据库会话、获取排行榜数据和向排行榜添加新条目的函数。

WEB_APP

+-- transactions.py
from models import Score
import uuid

def get_scores_txn(session):
    query = session.query(Score)
    return query.all()

def add_score_txn(session, avatar, playername, points):
    score = Score(
        id=str(
            uuid.uuid4()),
        avatar=avatar,
        playername=playername,
        points=points
    )
    session.add(score)

上面的代码使用SQLAlchemy提供的会话实例来查询和添加数据到数据库。使用像SQLAlchemy这样的ORM而不是直接使用SQL命令的一个好处是,我们可以在执行数据库操作的同时保持我们代码的面向对象编程(OOP)范式。

接下来,我们打开leaderboard.py 文件,将其内容替换为以下内容。

from cockroachdb.sqlalchemy import run_transaction
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from transactions import get_scores_txn, add_score_txn

class Leaderboard:
    def __init__(self, conn_string):
        self.engine = create_engine(conn_string, convert_unicode=True)
        self.sessionmaker = sessionmaker(bind=self.engine)

    def get_scores(self):
        return run_transaction(self.sessionmaker,
                               lambda session: self.prepare_scores(session))

    def add_score(self, score):
        return run_transaction(self.sessionmaker,
                               lambda session: add_score_txn(session, score.avatar, score.playername, score.points))

    def prepare_scores(self, session):
        scores = get_scores_txn(session)
        scores.sort(reverse=True, key=lambda e: e.points)

        result = list(map(lambda score, i: {'id': score.id,
                                            'ranking': i + 1,
                                            'avatar': self.get_avatar_dic()[str(score.avatar)],
                                            'playername': score.playername,
                                            'points': score.points
                                            },
                        scores,
                        list(range(len(scores)))))
        return result

    def get_avatar_dic(self):
        return {
        "0":"not set",
        "1":"👨🏻",
        "2":"👨🏼",
        "3":"👨🏽",
        "4":"👨🏾",
        "5":"👨🏿",
        "6":"👩🏻",
        "7":"👩🏼",
        "8":"👩🏽",
        "9":"👩🏾",
        "10":"👩🏿"
        }

我们修改了函数,以读取排行榜数据并使用SQLAlchemy会话添加新条目。此外,我们还修改了代码,用lambda函数对分数进行排序,因此最高分数总是排在排行榜的第一位。

最后,我们打开app.py 文件,用下面的代码片断替换其内容。

import flask
from flask import Flask, render_template, url_for
from flask_bootstrap import Bootstrap
from werkzeug.utils import redirect
from leaderboard import Leaderboard
from os import environ
from models import Score 

DEFAULT_ROUTE_LEADERBOARD = "index"
DEFAULT_ROUTE_PLAYER = "player"

app = Flask(__name__)
Bootstrap(app)

conn_string = environ.get("DB_URI")
leaderboard = Leaderboard(conn_string)

@app.route("/")
def index():

    scores = leaderboard.get_scores()
    return render_template("index.html",
                            scores=scores)

@app.route("/player", methods=["GET", "POST"])
def player():
    if flask.request.method == "POST":
        id = flask.request.values.get("id")
        avatar = flask.request.values.get("avatar")
        playername = flask.request.values.get("playername")
        points = flask.request.values.get("points")
        leaderboard.add_score(
            Score(id=id, avatar=avatar, playername=playername, points=points)
        )

        return redirect(url_for(DEFAULT_ROUTE_LEADERBOARD))
    else:
        avatars = leaderboard.get_avatar_dic()
        score = Score(avatar="0", playername="", points=0)
        return render_template("player.html", score = score, avatars = avatars)

在这里,我们修改了player 函数,以根据请求方法采取行动。如果用户通过浏览器(使用HTTP GET)点击/player 路径,我们告诉Flask引擎渲染player.html页面。如果用户通过点击确认按钮(通过HTTP POST)来请求/player 路径,我们就会从请求表单数据中获得分数信息,更新排行榜,并将用户重定向到主页。

最后,我们重新运行应用程序,以确认我们成功地将我们的网络应用程序与CockroachDB数据库集成。

flask run

SQLAlchemy with CockroachDB

现在,我们点击添加新条目按钮。

SQLAlchemy with CockroachDB

我们选择一个头像,并输入姓名和积分的其他字段。

SQLAlchemy with CockroachDB

现在,我们点击 "确认",在排行榜页面上看到新条目。

SQLAlchemy with CockroachDB

部署CockroachDB应用程序

在这一点上,我们的网络应用已经在本地运行了,这也是我们要做的。将我们的网络应用程序部署到云端,让它在世界任何地方都能运行,这才是锦上添花。

我们的选择之一是Heroku,这是一个平台即服务(PaaS),使开发者能够完全在云中构建、运行和操作应用程序。Heroku服务支持几种编程语言,包括Python。

首先,如果你还没有注册一个免费的Heroku账户。或者用你现有的账户登录。

Deploy CockroachDB with Heroku

当我们登录后,我们点击新建按钮,然后点击创建新的应用程序

Deploy CockroachDB with Heroku

我们为我们的应用程序命名(例如,web-app-python-cockroachdb),然后点击创建应用程序

Deploy CockroachDB with Heroku

接下来,我们选择 "设置"选项卡。

Deploy CockroachDB with Heroku

现在让我们添加DB_URI 环境变量,其值与我们在.env 本地文件中使用的相同,这样我们的新Heroku应用就可以从CockroachDB无服务器访问数据库URI。

Deploy CockroachDB with Heroku

为了在Heroku上提供我们的Flask应用,我们需要一个像Gunicorn这样的Web服务器网关接口(WSGI)来使Heroku能够将请求转发给我们的Python Web应用。我们使用以下命令安装Gunicorn。

pip install gunicorn

现在我们在项目根目录下创建一个名为Procfile(无扩展名)的文件,并添加以下一行。

web: gunicorn app:app

第一个app是运行我们应用程序的Python文件的名称或模块的名称。第二个应用是我们应用程序的名称。

我们还需要一个需求文件,使Heroku能够检测到我们的应用程序是一个Python项目。我们在根目录下运行以下命令,在根目录下生成requirements.txt文件。

pip freeze >requirements.txt

然后,我们下载并安装Heroku CLI。我们可以使用Heroku命令行界面(CLI)来管理、扩展和查看应用日志。安装CLI后,我们可以从我们的shell中使用heroku命令。

接下来,我们输入以下命令来登录我们的Heroku账户,然后按照提示创建一个新的SSH公钥。

heroku login

heroku登录命令打开我们的网络浏览器,引导我们进入Heroku登录页面。如果你还没有登录到你的浏览器,只需点击Heroku登录页面上的登录按钮。

Deploy CockroachDB with Heroku

herokugit 命令都需要这种认证才能正常工作。

接下来,我们将把我们的应用程序部署到Heroku。如果你已经有一个存储库,跳过这第一步。否则,去你的本地仓库文件夹,运行以下命令来初始化一个Git仓库。

git init

由于我们已经有一个Heroku应用,我们可以用下面的命令向本地仓库添加一个远程。

heroku git:remote -a YOUR-HEROKU-APP-NAME

然后,我们将项目文件分阶段,将我们的代码提交到仓库,并使用Git将其部署到Heroku。

git add .

git commit -am "this is a heroku commit"

接下来,我们将本地主分支的修改推送到Heroku的主分支。

git push heroku main

现在,我们进入Heroku仪表板,点击**"打开应用程序**",在一个新的浏览器标签中打开网络应用。

Deploy CockroachDB with Heroku

你会注意到,部署在Heroku上的网络应用的显示方式与你本地开发机上的一模一样。

Deploy CockroachDB with Heroku

如果你的Heroku安装有问题,你可能想对你的网络应用的部署、执行或配置进行故障排除。幸运的是,Heroku跟踪你的应用程序的所有活动,并使你能够浏览你的应用程序和Heroku组件产生的日志。要查看关于你的应用程序的大部分活动,请使用以下Heroku日志命令。

heroku logs --tail:

接下来的步骤

云数据库即服务(DBaaS)对于想要实现现代开发堆栈优势的公司来说是一项关键技术,包括提高安全性、加快部署、快速配置和业务敏捷性。而现在你已经学会了如何编写Python代码来快速创建一个Flask网络应用,依靠CockroachDB和CockroachDB无服务器云数据库,其中一些优势变得更加明显了。CockroachDB的无服务器产品极大地简化了数据库操作,即使从开发者的角度来看也是如此。

正如我们所看到的,将CockroachDB作为简单而完整的全栈网络应用的一部分,使用Python、Flask和Heroku是相当容易的。在Python中使用CockroachDB是很简单的,因为熟悉的Postgres工具和库可以开箱即用。

另外,将强大的对象关系映射工具SQLAlchemy与CockroachDB整合在一起也很容易,这对于已经熟悉PostgreSQL数据库的开发者来说是一个令人愉快的惊喜。

要继续学习我们在本文中介绍的技术并提高你的Python开发技能,请查看这些资源。

利用你的新技能,建立一个向你的组织的销售人员显示客户联系信息的应用,一个列出你朋友的手工作品的购物应用,或者任何你能想象到的依赖于从数据库中检索信息的应用。

你可以在这里注册一个免费的CockroachDB无服务器账户,以便迅速开始构建你自己的由CockroachDB支持的Python网络应用。