Tips for Better Programming in Python with Flask and Do
作者:禅与计算机程序设计艺术
1.简介
Python是一种广受欢迎的编程语言。
它独特的语法结构使程序逻辑更加清晰。
灵活的数据类型和强大的内置函数库使其功能丰富。
支持面向对象编程使其在多个领域具有广泛的应用潜力。
近年来,在机器学习和数据科学等新兴技术迅速发展的推动下,
在Web开发领域中逐渐得到了广泛应用。
Flask 是一个轻量级且高效的 Python Web 框架,
能够快速构建 RESTful API 服务。
Docker 是目前最流行且广泛应用的容器技术,
可以打包运行应用程序。
通过结合 Docker 和 Flask 技术,
我们可以构建功能强大且高可用性的 Web 应用。
本文将分享一些使用 Python+Flask+Docker 开发 Web 应用时的一些建议和技巧。
2.相关技术栈
在本文中,主要涉及以下技术栈:
- Python
- Flask
- SQLite
- Docker
- RESTful API
其中,在后端编程中使用Python,在Web开发中使用Flask充当了Python Web框架的工具;使用SQLite实现了关系型数据库功能;在容器化部署中使用Docker进行资源隔离与高效运行;而RESTful API则是基于HTTP协议定义的一种网络通信规范。
3.准备工作
安装环境
首先,安装好Python、Pipenv、SQLite以及Docker。
sudo apt update && sudo apt install python3-pip sqlite3 docker.io
pip3 install pipenv==2018.11.26
代码解读
创建一个新的虚拟环境并激活:
mkdir flaskapp && cd flaskapp
python3 -m venv.venv
source.venv/bin/activate
代码解读
配置项目目录
创建如下项目目录:
├── app
│ ├── __init__.py
│ └── routes.py
└── Dockerfile
代码解读
初始化项目
进入项目根目录,初始化项目:
pipenv --python 3.7
pipenv shell
pipenv install flask
pipenv run flask init
代码解读
然后,创建一个名为Dockerfile的文件,内容如下:
FROM python:3.7
WORKDIR /usr/src/app
COPY Pipfile Pipfile.lock./
RUN pipenv lock --requirements > requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY app app
CMD ["pipenv", "run", "flask", "run"]
代码解读
此Dockerfile文件指定了运行环境的配置方案,并包含了以下几方面的内容:一是安装必要的依赖项;二是设置了工作目录位置;三是复制了代码文件内容。此外还包含了启动命令和其他相关操作。
再创建一个名为app/__init__.py的文件,内容为空。
最后,创建一个名为app/routes.py的文件,内容如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True)
代码解读
这个文件生成并命名为hello_world()的视图函数,并输出字符串“Hello World!”。此外,在该文件中还添加了一个if __name__ == '__main__':语句来判断当前是否为主模块,并在满足条件时启动Flask程序。
至此,项目目录准备完毕。
4.配置文件
一般情况下而言,在Web应用开发过程中往往需要将一些关键的设置和参数存储到外部的配置文件中。例如常用的如数据库用户名和密码等信息都是常见的存储位置。
为了便于管理这些设置和参数值,Flask框架提供了config模块的支持。它不仅支持通过一个.py文件来定义项目所需的各种核心参数,并且在程序运行时能够以类似于字典对象的形式进行灵活地读取和修改。
创建一个名为config.py的文件,内容如下:
class Config:
SECRET_KEY ='secret key'
SQLALCHEMY_DATABASE_URI ='sqlite:///test.db'
class DevelopmentConfig(Config):
DEBUG = True
class ProductionConfig(Config):
pass
config = {
'development': DevelopmentConfig(),
'production': ProductionConfig()
}
代码解读
在该文件中,默认配置由类Config实现。这些子类包括DevelopmentConfig和ProductionConfig。它们继承自基类Config。它们分别对应着开发环境和生产环境的配置设置。此外,在一个叫做config的对象中存储了不同环境下相应的配置数据。
修改app/__init__.py文件,内容如下:
from config import config
from flask import Flask
import os
def create_app(env):
conf = config[env]
app = Flask(__name__)
app.config['SECRET_KEY'] = conf.SECRET_KEY
app.config['SQLALCHEMY_DATABASE_URI'] = conf.SQLALCHEMY_DATABASE_URI
#... more configs here
from app.routes import *
return app
代码解读
该函数接收一个名为 env 的参数;根据输入提供的环境名称,在 config 字典中检索相应的配置项;并完成对 Flask 程序全局变量 app 的设置;特别指出的是,在此场景下无需在配置文件中标明 DEBUG 属性;因为在这里可以通过配置对象灵活设定。
至此,配置文件编写完成。
5.数据库配置
在实际应用中,对数据进行持久化存储是一个常见的做法。例如我们需要保存用户信息、产品订单记录等关键数据项。为此我们需要安装并配置SQLAlchemy这一Python中的ORM框架
首先,安装SQLAlchemy:
pipenv install sqlalchemy
代码解读
然后,修改app/__init__.py文件,引入SQLAlchemy和models模块:
from config import config
from flask import Flask
import os
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(env):
conf = config[env]
app = Flask(__name__)
app.config['SECRET_KEY'] = conf.SECRET_KEY
app.config['SQLALCHEMY_DATABASE_URI'] = conf.SQLALCHEMY_DATABASE_URI
db.init_app(app)
# Import models here
from app.routes import *
return app
代码解读
接下来,创建models模块,定义数据库模型:
from app import db
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return '<User %s>' % self.username
class ProductOrder(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
product_name = db.Column(db.String(50))
quantity = db.Column(db.Integer)
user = db.relationship("User", backref="orders")
def __repr__(self):
return '<ProductOrder %s by %s>' % (self.product_name, self.user.username)
代码解读
在这个案例中, 我们创建了两个类型的实体: 用户实体(User)和产品订单实体(ProductOrder)。其中, 用户实体(User)存储了用户名. 邮箱等字段的信息, 并设置了主键字段id. 同时, 这个实体还包含了一个外连接字段products, 用于关联该用户的所有产品订单; 而产品订单实体(ProductOrder)则存储了购买者信息. 产品名称以及购买数量的数据. 此外, 这个实体还包含了一个指向对应用户的外连接字段user_id
接下来,修改app/__init__.py文件,加入以下两行代码:
from app.models import User, ProductOrder
代码解读
这样,就可以使用User和ProductOrder模型了。
6.路由配置
Web应用通常包含一系列页面或API接口,在线用户可以根据需求选择不同的URL发送请求给服务器。当客户端向服务器提交特定的URL时,在Flask框架中,默认会分析请求内容并返回相应结果。另外,在Flask中我们可以利用装饰器来定义路由,并且可以通过url_for()函数自动生成所需的URL路径以供访问。
创建一个名为views.py的文件,内容如下:
from flask import render_template, request
from app import app
from app.models import User, ProductOrder
@app.route('/', methods=['GET'])
def index():
users = User.query.all()
products = []
for u in users:
orders = u.orders
if len(orders) > 0:
p = {'name': orders[-1].product_name, 'quantity': orders[-1].quantity, 'by': u.username}
products.append(p)
return render_template('index.html', title='Home', message='Welcome to our website!', products=products)
@app.route('/add_order', methods=['POST'])
def add_order():
username = request.form['username']
password = request.form['password']
product_name = request.form['product_name']
quantity = int(request.form['quantity'])
# Check authentication credentials...
# Create or get the corresponding User object...
order = ProductOrder(user_id=user.id, product_name=product_name, quantity=quantity)
try:
db.session.add(order)
db.session.commit()
except Exception as e:
print(e)
db.session.rollback()
return redirect(url_for('error'))
flash('Your order has been recorded successfully.')
return redirect(url_for('index'))
代码解读
此文件提供了两个用于数据管理的视图函数:index()和add_order()。其中index()函数负责生成首页HTML模板,并从数据库中收集并返回所有用户及其最近一次下单的信息以供前端页面展示。add_order()则接收表单数据进行处理,在完成身份验证后(包括确认用户的认证信息),根据表单内容创建一个ProductOrder对象并将其保存到数据库中,并向操作结果界面反馈提示信息。
更新至app/routes.py文件中,并在对应的Flask蓝图参数设置中加入相应的路由配置
from app import views
from flask import url_for
from flask import Blueprint
bp = Blueprint('bp', __name__, url_prefix='/')
bp.add_url_rule('/', view_func=views.index, methods=['GET'])
bp.add_url_rule('/add_order', view_func=views.add_order, methods=['POST'])
代码解读
这样,所有的路由都由蓝图bp处理。
7.异常处理
Web应用在正常运行期间可能会遭遇多种异常情况,例如程序崩溃或网页访问失败等。为了避免出乎意料的问题影响服务可用性,我们需要建立一套严格完善的异常处理机制
Flask通过app.register_error_handler()方法来注册自定义错误处理器,并规定了该错误处理器的函数接口定义。
def register_error_handler(code_or_exception, f):
"""Registers a function to handle errors matching the given code or exception."""
代码解读
这表明我们能够指定一个错误代码或错误类型,并将其对应的处理函数配置到Flask的错误处理机制中。以下列举了几种常见的错误处理策略:
from flask import jsonify
@app.errorhandler(404)
def page_not_found(e):
return jsonify({'message': 'The requested URL was not found on the server.'}), 404
@app.errorhandler(Exception)
def unhandled_exception(e):
return jsonify({'message': str(e)}), 500
代码解读
该函数专门应对未被捕获的404错误情况并直接输出JSON格式的数据并设置状态码为404而另一个函数则负责处理未能被捕获的其他异常情况将异常信息转换为JSON格式后返回给客户端并将相应的状态码设置为500
8.测试
为了维护项目具有健壮性和可用性的需求,我们应当开发自动化测试用例以保证应用功能正常运行
创建一个名为tests.py的文件,内容如下:
import unittest
from app import app, db
from app.models import User, ProductOrder
class TestViews(unittest.TestCase):
def setUp(self):
self.client = app.test_client()
db.create_all()
u = User(username='admin', email='<EMAIL>')
u.set_password('<PASSWORD>')
db.session.add(u)
db.session.commit()
def tearDown(self):
db.drop_all()
def test_home(self):
response = self.client.get('/')
assert b'Welcome to our website!' in response.data
def test_add_order(self):
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
data = {'username': 'admin', 'password':'secret',
'product_name': 'iPhone XS', 'quantity': '10'}
response = self.client.post('/add_order', headers=headers,
data=data, follow_redirects=True)
assert b'Your order has been recorded successfully.' in response.data
assert b'iPhone XS' in response.data
代码解读
该文件包含了两个测试场景:一个主要针对首页页面的响应验证功能(即确认返回结果中包含欢迎信息),另一个则关注表单提交后的反馈机制(即确认成功提示信息的显示)。这两个主要测试分别对应于类名中的方法名称:TestViews中的test_home()和test_add_order()。
为了运行测试用例,修改app/__init__.py文件,在末尾新增以下两行代码:
if __name__ == '__main__':
db.create_all()
app.run(host='0.0.0.0', debug=True)
代码解读
这行代码依次执行了三项操作:首先进行了数据库的初始化;接着启动了Flask服务;随后扫描了所有可用的IP地址;最后确保能够进行远程连接测试。
最后,执行以下命令即可运行测试用例:
pipenv run python tests.py
代码解读
如果测试通过,输出结果如下:
Ran 2 tests in 0.109s
OK
Destroying database...
代码解读
9. Dockerization
在当前市场中,Docker已脱颖而出为最广泛使用的容器技术。
通过 Docker 使用者, 我们能够显著提升Web应用部署的速度与便捷性。
轻松构建 Docker 镜像文件, 完全避免了繁琐的环境配置与运维挑战。
创建一个名为docker-compose.yml的文件,内容如下:
version: '3'
services:
flaskapp:
build:
context:.
ports:
- "5000:5000"
environment:
FLASK_ENV: development
DATABASE_URL: "sqlite:tmp/flaskapp.db"
db:
image: postgres:alpine
restart: always
volumes:
- pgdata:/var/lib/postgresql/data/
env_file:
-./.env
volumes:
pgdata:
代码解读
此文件定义了两个服务:flaskapp和db。flaskapp服务基于Dockerfile构建,并配置了端口映射、设置了环境变量,并对容器进行卷绑定。db服务则基于PostgreSQL镜像构建,在设置为始终运行的重启策略下,并对容器进行卷绑定的同时也将环境变量文件也进行了卷绑定。
为方便管理环境变量,创建一个名为.env的文件,内容如下:
FLASK_APP=run.py
FLASK_ENV=production
DATABASE_URL=postgres://myuser:mypassword@db/mydatabase
代码解读
这个文件定义了Flask环境变量和数据库连接信息。
修改Dockerfile文件,内容如下:
FROM tiangolo/uwsgi-nginx-flask:python3.6
COPY./app /app
COPY.env.env
COPY uwsgi.ini /etc/uwsgi/
COPY supervisord.conf /etc/supervisor/supervisord.conf
RUN apk update \
&& apk add postgresql-dev gcc python3-dev musl-dev libffi-dev openssl-dev supervisor nginx git\
&& pip3 install psycopg2-binary gunicorn
RUN mkdir -p static/uploads/images static/uploads/videos static/uploads/audios
RUN chown -R nginx:root /app \
&& chmod -R go-w /app \
&& mkdir -p logs \
&& touch /var/log/nginx/access.log /var/log/nginx/error.log \
&& ln -sf /dev/stdout /app/logs/access.log \
&& ln -sf /dev/stderr /app/logs/error.log
EXPOSE 80
CMD ["/start.sh"]
代码解读
该Dockerfile基于tiangolo/uwsgi-nginx-flask:python3.6镜像构建,请包括了以下操作:首先配置了卷绑定设置;其次安装并配置了PostgreSQL客户端;接着配置了Nginx的日志绑定;最后设置了启动脚本,并创建了静态资源目录。
创建一个名为start.sh的文件,内容如下:
#!/bin/bash
export DJANGO_SETTINGS_MODULE=config.settings.$FLASK_ENV
exec gunicorn wsgi:app -c "/gunicorn_conf.py"
代码解读
该文件包含了启动指令。它将配置Django的环境变量。随后将使用Gunicorn来托管Flask应用程序。
创建一个名为supervisord.conf的文件,内容如下:
[program:nginx]
command=/usr/sbin/nginx
autostart=true
autorestart=true
stopsignal=QUIT
redirect_stderr=true
[program:gunicorn]
command=/start.sh
autostart=true
autorestart=true
numprocs=2
directory=/app
startretries=3
代码解读
该文档明确了Supervisor的配置文件内容,在详细说明其功能的同时指出其主要作用是在Nginx和Gunicorn服务出现异常退出的情况下自动发起重启动操作以确保服务稳定运行
修改app/__init__.py文件,内容如下:
from config import config
from flask import Flask
import os
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
def create_app(env):
conf = config[env]
app = Flask(__name__)
app.config['SECRET_KEY'] = conf.SECRET_KEY
app.config['SQLALCHEMY_DATABASE_URI'] = conf.SQLALCHEMY_DATABASE_URI
app.logger.setLevel(logging.INFO)
handler = logging.StreamHandler(sys.stdout)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
app.logger.addHandler(handler)
db.init_app(app)
@app.before_first_request
def initialize_database():
db.create_all()
from app.routes import bp
app.register_blueprint(bp)
return app
代码解读
该文件在运行阶段设定其日志级别参数为INFO级别,并将标准输出指定为其日志输出工具使用。
至此,Web应用开发的基础知识点已经全面展开,文章的核心内容有:
- 背景概述
- 基本术语解释
- 核心算法详细阐述了其原理及实施流程的同时深入解读了相关的数学表达式
- 详细代码实现与功能解析部分提供了具体的代码实例并对其运行逻辑进行了全面解析
- 发展动态及面临的挑战分析涵盖了当前技术领域的最新动态并指出了当前研究中可能遇到的技术瓶颈
- 常见问题汇总及其解答方案列出了常见技术难题并提供了解决方案的具体思路和实施路径
