Flask实战—使用工厂函数创建程序实例

在我们开始学习FLask的时候,创建应用的实例是用app=Flask(name)来做的,最初的写法如下:

#__init__.py里面创建实例,应用实例对象创建完再引入视图函数的模块,因为这时候视图函数上的@app.route()才有效
from flask import Flask
from app.api import bp as api_bp
import config

app = Flask(__name__)
app.config.from_object('config')
app.register_blueprint(api_bp, url_prefix='/api')

但是如果我们想创建不同配置的实例,这种写法改起来比较麻烦。为了减少麻烦,我们可以采用调用一个create_app函数来返回应用实例的方法,这就工厂函数的意思了。

在OOP(Object-Oriented Programming,面向对象编程)中,工厂(factory)是指创建其他对象的对象,通常是一个返回其他类的对象的函数或方法。按照惯例,这个函数被命名为create_app()或make_app()。我们把这个工厂函数称为程序工厂(Application Factory)--即“生产程序的工厂”,使用它可以在任何地方创建程序实例。

 

工厂函数使得测试和部署更加方便。我们不必将加载的配置写死在某处,而是直接在不同的地方按照需要的配置创建程序实例。通过支持创建多个程序实例,工厂函数提供

了很大的灵活性。另外,借助工厂函数,我们还可以分离扩展的初始化操作。创建扩展对象的操作可以分离到单独的模块,这样可以有效减少循环依赖的发生。

# app/__init__py
# -*- coding: utf-8 -*-

from flask import Flask
from app.api import bp as api_bp
from config import config

def create_app(config_name=None):
    app = Flask(__name__)
    configure_app(app, config_name)
    app.register_blueprint(api_bp, url_prefix='/api')

    return app
 
def configure_app(app, config_name=None):
    if config_name is None:
        config_name = os.environ.get('FLASK_ENV', 'development')
    if config_name in config:
        app.config.from_object(config[config_name])
#config.py

# -*- coding: utf-8 -*-

class BaseConfig:
    pass

class DevelopmentConfig(BaseConfig):
    pass

class TestingConfig(BaseConfig):
    pass

class ProductionConfig(BaseConfig):
    pass

config = {
    'development': DevelopmentConfig,
    'testing': TestingConfig,
    'production': ProductionConfig,
    'default': DevelopmentConfig
}

配置写在config.py文件中,有生产环境、开发环境、测试环境等不同环境的配置,具体的配置配置内容,暂时不写。

 

工厂函数接收配置名作为参数,返回创建好的程序实例。如果没有传入配置名,我们会从FLASK_ENV环境变量获取,如果没有获取到则使用默认值development。

在这个工厂函数中,我们会创建程序实例,然后为其加载配置,注册在前面创建的蓝本,最后返回程序实例。不过,现在的程序实例还没有执行扩展的初始化操作,后续一步步扩充它。

工厂函数一般在程序包的构造文件__init__.py中创建,也可以在程序包内新建的模块来存放,比如factory.py或是app.py。

 

在工厂函数,我们一般做加载配置、初始化扩展、注册蓝图、日志配置等工作。

1、加载配置
       工厂函数接收配置名称作为参数,这允许我们在程序的不同位置传入不同的配置来创建程序实例。比如,使用工厂函数后,我们可以在测试脚本中使用测试配置来调用工厂函数,创建一个单独用于测试的程序实例,而不用从某个模块导入程序实例。

 

2、初始化扩展

    为了完成扩展的初始化操作,我们需要在实例化扩展类时传入程序实例。但使用工厂函数时,并没有一个创建好的程序实例可以导入。如果我们把实例化操作放到工厂函数中,那么我们就没有一个全局的扩展对象可以使用,比如表示数据库的db对象。

     为了解决这个问题,大部分扩展都提供了一个init_app()方法来支持分离扩展的实例化和初始化操作。现在我们仍然像往常一样初始化扩展类,但是并不传入程序实例。这时扩展类实例化的工作可以集中放到extension.py脚本中,如下所示:

      

#app/extensions.py:扩展类实例化
# -*- coding: utf-8 -*-

from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from sqlalchemy import MetaData

# Flask-Cors plugin
cors = CORS()
# Flask-SQLAlchemy plugin
naming_convention = {
    "ix": 'ix_%(column_0_label)s',
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(column_0_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s"
}
db = SQLAlchemy(metadata=MetaData(naming_convention=naming_convention))
# Flask-Migrate plugin
migrate = Migrate(render_as_batch=True)

现在,当我们需要在程序实例中使用扩展对象时,直接从这个extensions模块导入即可。在工厂函数中,我们导入所有扩展对象,并对其调用init_app()方法,传入程序实例完成初始化操作:

# app/__init__py
# -*- coding: utf-8 -*-

from flask import Flask
from app.api import bp as api_bp
from config import config
from app.extension import cors, db, migrate

def create_app(config_name=None):
    app = Flask(__name__)

    configure_app(app, config_name)
    configure_extensions(app)
    configure_blueprints(app)
    configure_logger(app)
    configure_shell_context(app)

    return app
 
def configure_app(app, config_name=None):
    if config_name is None:
        config_name = os.environ.get('FLASK_ENV', 'development')
    if config_name in config:
        app.config.from_object(config[config_name])

def configure_extensions(app):
    # Enable CORS
    cors.init_app(app)
    # 注册数据库连接
    db.app = app
    db.init_app(app)
    # Init Flask-Migrate
    migrate.init_app(app, db)

def configure_blueprints(app):
    app.register_blueprint(api_bp, url_prefix='/api')

def configure_logger(app):
    pass

def configure_errors(app):
    pass

def configure_shell_context(app):
    @app.shell_context_processor
    def make_shell_context():
        return dict(db = db)
 

现在,当工厂函数被调用后。首先创建一个特定配置类的程序实例,然后执行一系列configue_*配置函数为程序实例配置扩展、蓝本、日志、上下文处理器、请求处理器。。。在这个程序工厂的加工流水线的尽头,我们可以得到一个包含所有基本组件的可以直接运行的程序实例。

 

当使用工厂函数时,因为扩展初始化操作分离,db.create_all()将依赖于程序上下文才能正常执行。执行flask shell命令启动的python shell会自动激活程序上下文,flask命令也会默认在程序上下文环境下执行,所以目前程序中的db.create_all()方法可以被正确执行。当在其他脚本中直接调用db.create_all(),或是在普通的python shell中调用时,则需要手动激活程序上下文。

 

3.启动程序

当使用flask run命令启动程序时,Flask的自动发现程序实例机制还包含另一种行为:flask会自动从环境变量FLASK_APP的值定义的模块中寻找名称为create_app()或make_app()的工厂函数,自动调用工厂函数创建程序实例并运行。因为我们已经在.flaskenv文件中将FLASK_APP设为app,所以不需要更改任何设置,继续使用flask run命令即可运行程序

如果想设置特定的配置名称,最简单的方式是通过环境变量FLASK_ENV设置。另外,你也可以使用FLASK_APP显示地指定工厂函数并传入参数:

FLASK_APP = "app:create_app('development')"

为了支持Flask自动从FLASK_APP环境变量对应值指向的模块或包中发现工厂函数,工厂函数中接收的参数必须是默认参数,即设置了默认值的参数,比如“config_name=None”。

 

 

4. current_app

使用工厂函数后,我们会遇到一个问题:对于蓝本实例没有提供,程序实例独有的属性和方法应该如何调用呢(比如获取配置的app.config属性)?考虑下面的因素:

   1-使用工厂函数创建程序实例后,在其他模块中并没有一个创建好的程序实例可以让我们导入使用。
   2-使用工厂函数后,程序实例可以在任何地方被创建。你不能固定导入某一个程序实例,因为不同程序实例可能加载不同的配置变量。

解决方法是使用current_app对象,它是一个表示当前程序实例的代理对象。当某个程序实例被创建并运行时,它会自动指向当前运行的程序实例,并把所有操作都转发到当前的程序实例。比如,当我们需要获取配置值时,会使用current_app.config,其他方法和属性也一样。

current_app是程序上下文全局变量,所以只有在激活了程序上下文之后才可以使用。比如在视图函数中,或是在视图函数中调用的函数和对象中。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的SPI Flash读写驱动程序示例,使用的是Linux内核自带的SPI驱动接口。 #include <linux/module.h> #include <linux/spi/spi.h> #include <linux/mtd/mtd.h> #include <linux/mtd/partitions.h> #define FLASH_PAGE_SIZE 256 #define FLASH_SECTOR_SIZE 4096 #define FLASH_BLOCK_SIZE 65536 struct spi_flash { struct mtd_info mtd; struct spi_device *spi; }; static int spi_flash_probe(struct spi_device *spi) { struct spi_flash *flash; struct mtd_partition *parts; int nparts, err; flash = kzalloc(sizeof(struct spi_flash), GFP_KERNEL); if (!flash) { dev_err(&spi->dev, "Failed to allocate memory for spi_flash\n"); return -ENOMEM; } flash->spi = spi; /* Set up MTD structure */ flash->mtd.name = spi->modalias; flash->mtd.owner = THIS_MODULE; flash->mtd.type = MTD_NORFLASH; flash->mtd.flags = MTD_CAP_NORFLASH; flash->mtd.erasesize = FLASH_BLOCK_SIZE; flash->mtd.writesize = FLASH_PAGE_SIZE; flash->mtd.writebufsize = FLASH_PAGE_SIZE; /* Register MTD device */ err = mtd_device_register(&flash->mtd, NULL, 0); if (err) { dev_err(&spi->dev, "Failed to register MTD device, error %d\n", err); kfree(flash); return err; } /* Set up partition table */ nparts = get_mtd_device_partitions(&flash->mtd, &parts, 0); if (nparts <= 0) { dev_err(&spi->dev, "Failed to create partition table\n"); mtd_device_unregister(&flash->mtd); kfree(flash); return -EINVAL; } /* Print information about the device */ dev_info(&spi->dev, "SPI Flash device detected, %d partitions.\n", nparts); dev_info(&spi->dev, "Flash device size %llu bytes, erase size %d bytes, write size %d bytes.\n", (unsigned long long)flash->mtd.size, flash->mtd.erasesize, flash->mtd.writesize); return 0; } static int spi_flash_remove(struct spi_device *spi) { struct spi_flash *flash = spi_get_drvdata(spi); mtd_device_unregister(&flash->mtd); kfree(flash); return 0; } static const struct of_device_id spi_flash_of_match[] = { { .compatible = "spansion, s25fl064k", }, { /* end of table */ } }; MODULE_DEVICE_TABLE(of, spi_flash_of_match); static struct spi_driver spi_flash_driver = { .driver = { .name = "spi_flash", .owner = THIS_MODULE, .of_match_table = spi_flash_of_match, }, .probe = spi_flash_probe, .remove = spi_flash_remove, }; static int __init spi_flash_init(void) { return spi_register_driver(&spi_flash_driver); } static void __exit spi_flash_exit(void) { spi_unregister_driver(&spi_flash_driver); } module_init(spi_flash_init); module_exit(spi_flash_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("SPI Flash driver"); MODULE_ALIAS("spi:spi_flash"); 以上代码定义了一个名为spi_flash的驱动程序,它基于Linux内核自带的SPI驱动接口,并提供了一个MTD设备,可以进行SPI Flash的读写操作。在probe函数中,驱动程序会初始化MTD设备,并创建分区表。在remove函数中,驱动程序会注销MTD设备。在spi_flash_init函数中,驱动程序会注册SPI驱动程序。在spi_flash_exit函数中,驱动程序会注销SPI驱动程序

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X-Programer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值