在这篇文章中,我们要构建一个全栈式的网络应用程序,模拟一个游戏排行榜。我们的想法是让它尽可能的简单--一个页面显示排行榜列表,另一个页面添加新的排行榜条目。
作为一个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)
使用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 }} {{ 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
我们点击添加新条目,测试是否显示第二页来收集新玩家的数据。
我们已经成功创建了一个收集和显示球员分数的应用程序。
设置CockroachDB无服务器账户
我们仍然需要用实际数据库中的排行榜信息来替换假数据。按照下面的步骤来设置一个新的CockroachDB数据库集群。
- 如果你还没有,请注册一个CockroachDB无服务器账户。
- 登录到你的CockroachDB Serverless账户。
- 在集群页面上,点击创建集群。
- 在创建集群页面上,选择CockroachDB Serverless。
- 点击创建你的免费集群。
注意,一旦你完成了账户注册并创建了一个新的集群,CockroachDB就会向你展示一个像下面这样的连接字符串。
注意,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的数字,每个都代表一个表情符号的脸。这里是分数表模式的图解。
创建表最常见的方法之一是运行一个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
现在,我们点击添加新条目按钮。
我们选择一个头像,并输入姓名和积分的其他字段。
现在,我们点击 "确认",在排行榜页面上看到新条目。
部署CockroachDB应用程序
在这一点上,我们的网络应用已经在本地运行了,这也是我们要做的。将我们的网络应用程序部署到云端,让它在世界任何地方都能运行,这才是锦上添花。
我们的选择之一是Heroku,这是一个平台即服务(PaaS),使开发者能够完全在云中构建、运行和操作应用程序。Heroku服务支持几种编程语言,包括Python。
首先,如果你还没有注册一个免费的Heroku账户。或者用你现有的账户登录。
当我们登录后,我们点击新建按钮,然后点击创建新的应用程序。
我们为我们的应用程序命名(例如,web-app-python-cockroachdb),然后点击创建应用程序。
接下来,我们选择 "设置"选项卡。
现在让我们添加DB_URI 环境变量,其值与我们在.env 本地文件中使用的相同,这样我们的新Heroku应用就可以从CockroachDB无服务器访问数据库URI。
为了在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登录页面上的登录按钮。
heroku 和git 命令都需要这种认证才能正常工作。
接下来,我们将把我们的应用程序部署到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仪表板,点击**"打开应用程序**",在一个新的浏览器标签中打开网络应用。
你会注意到,部署在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网络应用。