Flask和FastAPI在目前的工作中是老生常谈的话题了,不过一直都是上手就用,遇到不会的就看文档,知识比较散落,并且容易忘记。因此这次做个整理,将Flask中一些常用的概念记录一下。
基本用法 基本用法不再赘述,流程就是先实例化app
,使用Flask定义的装饰器来定义路由,最后设置app
的监听端口等配置项:
1 2 3 4 5 6 7 8 9 10 from flask import Flask app = Flask(__name__)@app.route('/' ) def hello_world (): return 'Hello, World!' if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8080 , debug=True )
上面的代码有两种执行方法:
直接python 文件名.py:此时会在本机IP的8080端口起服务。
命令行设置环境变量,用flask run
执行。例如以下:
1 2 export FLASK_APP=myapplication.py flask run --host=0.0 .0 .0 --port=8080
基本的HTTP方法 几种常用的方式能够定义,例如路由方法直接使用:
1 2 3 4 5 6 7 8 from flask import request@app.route('/login' , methods=['GET' , 'POST' ] ) def login (): if request.method == 'POST' : return do_the_login() else : return show_the_login_form()
或者app
实例内置的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Flask, request app = Flask(__name__)@app.get('/hello' ) def hello_world (): return 'Hello, World!' @app.post('/upload' ) def upload_file (): file = request.files['file' ] file.save('/tmp/' + file.filename) return 'File uploaded'
读取配置项构建应用 实际较大的项目中,需要配置诸多选项,这时如果只是用上面的方法构建实例app
,那就会比较不灵活,这时能够通过定义应用工厂来实现。所谓应用工厂对应的是设计模式里的工厂函数。以下是官方示例代码:
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 import osfrom flask import Flaskdef create_app (test_config=None ): app = Flask(__name__, instance_relative_config=True ) app.config.from_mapping( SECRET_KEY='dev' , DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite' ), ) if test_config is None : app.config.from_pyfile('config.py' , silent=True ) else : app.config.from_mapping(test_config) try : os.makedirs(app.instance_path) except OSError: pass @app.route('/hello' ) def hello (): return 'Hello, World!' return app
执行flask --app flaskr run --debug
来运行应用。这时能够在默认5000
访问到hello
函数。
实际场景 以下是一个较为实际的项目场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from flask import Flask, requestfrom config import Configclass ServiceApp (Flask ): pass def create_app (): app = ServiceApp(__name__) app.config.from_object(Config()) logging.basicConfig(level=app.config.get('LOG_LEVEL' , 'INFO' )) initialize_extensions(app) register_blueprints(app) register_commands(app) return app app = create_app()if __name__ == '__main__' : app.run(host='0.0.0.0' , port=5000 )
上面的initialize_extensions
和注册函数暂且不看,我们在app.config.from_object
的入参是一个Config()
对象,内容大致如下:
1 2 3 4 5 6 7 8 class Config : def __init__ (self ): self.CURRENT_VERSION = "0.0.1" self.COMMIT_SHA = get_env('COMMIT_SHA' ) self.EDITION = "SELF_HOSTED" self.DEPLOY_ENV = get_env('DEPLOY_ENV' ) self.TESTING = False self.LOG_LEVEL = get_env('LOG_LEVEL' )
数据库定义 和Web应用最紧密相关的无疑是数据库的连接,这里我们假设拥有一个Flask项目,我们在这个项目下构建一个文件夹/db
,并在文件夹下新建db.py
。
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 import sqlite3import clickfrom flask import current_app, g def get_db (): if 'db' not in g: g.db = sqlite3.connect( current_app.config['DATABASE' ], detect_types=sqlite3.PARSE_DECLTYPES ) g.db.row_factory = sqlite3.Row return g.dbdef close_db (e=None ): db = g.pop('db' , None ) if db is not None : db.close()
这里的get_db
方法用来获取数据库连接,不过目前数据库里还没有数据。我们需要创建数据。
首先在/db
路径下创建一个schema.sql
,里面存储一些创建表的SQL命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 DROP TABLE IF EXISTS user; DROP TABLE IF EXISTS post; CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password TEXT NOT NULL ); CREATE TABLE post ( id INTEGER PRIMARY KEY AUTOINCREMENT, author_id INTEGER NOT NULL, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, title TEXT NOT NULL, body TEXT NOT NULL, FOREIGN KEY (author_id) REFERENCES user (id ) );
随后在/db/db.py
中加入执行这段命令的Python代码:
1 2 3 4 5 6 7 8 9 10 11 12 def init_db (): db = get_db() with current_app.open_resource('schema.sql' ) as f: db.executescript(f.read().decode('utf8' ))@click.command('init-db' ) def init_db_command (): """Clear the existing data and create new tables.""" init_db() click.echo('Initialized the database.' )
这段代码的核心是init_db
,它首先使用get_db
方法连接了数据,随后使用executescript
执行了创建数据表的SQL命令。
随后,通过click
库中的command
方法,定义了一个init-db
的方法。当执行flask init-db
时,就会执行init_db_command
函数,也就是数据表的初始化。
注册应用 上面两个函数现在我们只是写好了,但是并没有和我们的Flask应用相关联,因此我们需要进行注册。
目前为止,我们对数据库的操作方法都在db.py
中,同样的,数据库初始化我们也放在这里。我们加入一个新的方法init_app
,它用来执行数据库初始化以及正常关闭。
1 2 3 4 def init_app (app ): app.teardown_appcontext(close_db) app.cli.add_command(init_db_command)
随后,我们在Flask应用初始化的位置(例如__init__.py
)引入这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 def create_app (): app = Flask(__name__, instance_relative_config=True ) app.config.from_mapping( SECRET_KEY='dev' , DATABASE=os.path.join(app.instance_path, 'flaskr.sqlite' ), ) app.config.from_pyfile('config.py' , silent=True ) from . import db db.init_app(app) return app
这样,初始化数据库函数就在应用中进行了注册,我们使用flask --app flaskr init-db
既可初始化。
实际场景 实际场景中,我们项目中会使用SQLAlchemy
来实例化数据库。这是一个 Python ORM 框架,用于将数据库操作映射为 Python 对象的操作。
1 2 3 4 5 6 from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()def init_app (app ): db.init_app(app)
同时,我们需要在Flask应用中加上它的配置参数,我们有两种方法:
1 2 3 4 5 6 from flask import Flaskfrom db_module import init_app app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI' ] = 'sqlite:///example.db' init_app(app)
通常SQLALCHEMY_DATABASE_URI
的格式如下:
1 dialect+driver://username:password@host:port/database
dialect :SQLAlchemy 使用的数据库类型,如 postgresql
, mysql
, sqlite
等。driver :连接数据库时使用的 DBAPI,这通常可以省略。username :连接数据库的用户名。password :对应的密码。host :数据库服务器的地址。port :数据库服务器的端口。database :要连接的数据库名称。
面对如此多的参数,我们可以在Config
类中定义:
1 2 3 4 5 6 7 class Config : def __init__ (self ): db_credentials = { key: get_env(key) for key in ['DB_USERNAME' , 'DB_PASSWORD' , 'DB_HOST' , 'DB_PORT' , 'DB_DATABASE' , 'DB_CHARSET' ] } self.SQLALCHEMY_DATABASE_URI = f"postgresql://{db_credentials['DB_USERNAME' ]} :{db_credentials['DB_PASSWORD' ]} @{db_credentials['DB_HOST' ]} :{db_credentials['DB_PORT' ]} /{db_credentials['DB_DATABASE' ]} {db_extras} "
随后在Flask应用的设置中引入参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from flask import Flask, requestfrom config import Configclass ServiceApp (Flask ): pass def create_app (): app = ServiceApp(__name__) app.config.from_object(Config()) logging.basicConfig(level=app.config.get('LOG_LEVEL' , 'INFO' )) db.init_app(app) return app app = create_app()
蓝图 什么是蓝图?在Flask中,我们可以把它看作是一个Flask应用的子集。
在我们之前的代码中,我们都在为主Flask应用注册路由。例如hello_world
方法通过主路由调用。如果项目中包含了多个应用,这时只通过主路由调用就不那么优雅了。Flask提供的蓝图方法支持将应用分解成不同的组件,每个组件都用来处理单独的操作,最后在主路由上统一注册。
以下是一个简单示例,例如如果想创建一个处理用户相关的蓝图,我们需要实现以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 from flask import Blueprint users = Blueprint('users' , __name__)@users.route('/login' ) def login (): return 'Login Page' @users.route('/logout' ) def logout (): return 'Logout Page'
随后,我们需要在主Flask应用中对蓝图进行注册:
1 2 3 4 5 6 from flask import Flaskfrom module import users app = Flask(__name__) app.register_blueprint(users, url_prefix='/users' )
当我们注册蓝图后,就可以在/users/login
端口进行访问。这就是url_prefix
的作用。它的好处就是让应用后加模块化。
实际场景 以下是一个场景:
首先我们实例化蓝图对象,并设置对应的路由:
1 2 3 4 5 from flask import Blueprintfrom libs.external_api import ExternalApi bp = Blueprint('web' , __name__, url_prefix='/api' ) api = ExternalApi(bp)
这里的ExternalApi
是我们定义的对象,它继承自flask_restful
的Api
对象,并覆写了handle_error
方法以处理报错信息。
我们会在这个api
中不断添加资源,代码类似于api.add_resource(CompletionApi, '/chat/completions')
。
随后我们在主应用中注册蓝图:
1 2 3 4 5 6 7 8 9 10 11 12 def register_blueprints (app ): from controllers.web import bp as web_bp app.register_blueprint(web_bp) def create_app (): app = Flask(__name__) app.config.from_object(Config()) logging.basicConfig(level=app.config.get('LOG_LEVEL' , 'INFO' )) register_blueprints(app) return app
整体的顺序如下:
初始化API示例,也就是api = Api(bp)
注册蓝图至Flask应用,即app.register_blueprint(bp)
创建API实例后,向实例添加资源。
2024/6/1 于苏州