正文

程序包结构

——————————————————————————————————
flask文件夹结构

其中:
app为程序包,Flask程序保存在这个包中
migrations文件夹包含数据库迁移脚本
tests包保存单元测试
requirements文件夹中记录程序的依赖
config.py是程序的配置文件
manage.py是程序的运行文件,用于启动程序即程序的其他任务

——————————————————————————————————

app包结构  

其中:
auth为保存专门用于认证的auth蓝本
main为保存main蓝本的包
static文件夹用于保存静态文件,例如HTML代码中引用的图片、 JavaScript 源码文件和 CSS
templates用于保存网页的模板

———————————————————————————————————————

蓝本文件夹结构(以auth文件夹为例,main蓝本等不再赘述)

蓝本的细节就不在此赘述,在flask框架中用到蓝本,可以对不同的程序功能使用不同的蓝本,这是保证程序整齐有序的办法。(想想把所有功能都写在一起会多么混乱)。这里说明一下到蓝本的程序运行原理:app/auth/views.py 模块引入蓝本,然后使用蓝本的 route 修饰器定义与认证相关的路由,然后再渲染views中设定的网页模板。看起来是不是如果程序能运行到蓝本这一步,我们就可以对网页进行操作了。
2. 运行说明
在运行程序的时候,我们在虚拟环境下,通过如下命令来完成。由此可见,程序的运行是由manage.py来开始的

(venv) $ python manage.py runserver

那么,我们来看看这个manage.py吧,期待不期待?兴奋不兴奋?

#!/usr/bin/env python
import os
COV = None
if os.environ.get('FLASK_COVERAGE'):
import coverage
COV = coverage.coverage(branch=True, include='app/*')
COV.start() if os.path.exists('.env'):
print('Importing environment from .env...')
for line in open('.env'):
var = line.strip().split('=')
if len(var) == 2:
os.environ[var[0]] = var[1] from app import create_app, db
from app.models import User, Follow, Role, Permission, Post, Comment
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app)
migrate = Migrate(app, db) def make_shell_context():
return dict(app=app, db=db, User=User, Follow=Follow, Role=Role,
Permission=Permission, Post=Post, Comment=Comment)
manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command('db', MigrateCommand) @manager.command
def test(coverage=False):
"""Run the unit tests."""
if coverage and not os.environ.get('FLASK_COVERAGE'):
import sys
os.environ['FLASK_COVERAGE'] = ''
os.execvp(sys.executable, [sys.executable] + sys.argv)
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
if COV:
COV.stop()
COV.save()
print('Coverage Summary:')
COV.report()
basedir = os.path.abspath(os.path.dirname(__file__))
covdir = os.path.join(basedir, 'tmp/coverage')
COV.html_report(directory=covdir)
print('HTML version: file://%s/index.html' % covdir)
COV.erase() @manager.command
def profile(length=25, profile_dir=None):
"""Start the application under the code profiler."""
from werkzeug.contrib.profiler import ProfilerMiddleware
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[length],
profile_dir=profile_dir)
app.run() @manager.command
def deploy():
"""Run deployment tasks."""
from flask_migrate import upgrade
from app.models import Role, User # migrate database to latest revision
upgrade() # create user roles
Role.insert_roles() # create self-follows for all users
User.add_self_follows() if __name__ == '__main__':
manager.run()

下面我们按照顺序去理一下,看看manage.py到底是如何运行的。

import os
COV = None
if os.environ.get('FLASK_COVERAGE'):
import coverage
COV = coverage.coverage(branch=True, include='app/*')
COV.start()

这段代码是关于程序测试覆盖度方面的,我们可以先行略去,测试本身并不影响程序的运行。

if os.path.exists('.env'):
print('Importing environment from .env...')
for line in open('.env'):
var = line.strip().split('=')
if len(var) == 2:
os.environ[var[0]] = var[1]

这段代码的作用是从.env文件中导入环境变量。具体的大家可以去搜索一下这个文件,在程序配置中,有些信息是在环境变量中设置的。但是此处我们也略过细节。

from app import create_app, db
from app.models import User, Follow, Role, Permission, Post, Comment
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand

各种导入,从app包中导入create_app工厂函数和数据库db。从app.models模块中导入User, Follow, Role, Permission, Post, Comment等类。在flask_script扩展中导入Manager, Shell, 从flask_migrate扩展中导入Migrate, MigrateCommand。什么意思呢?自己去搜索一下。
此处有一个坑:在原文中flask的扩展采用from flask.ext.script import Manager, Shell的导入方式,但是实际用的时候却会报错,从报错的信息中可以知道flask.ext.script已经弃用了,改为flask_script即可。其他的扩展也是一样。

>>> from flask.ext.script import Manager
__main__:1: ExtDeprecationWarning: Importing flask.ext.script is deprecated, use flask_script instead.

这一句是创建工厂函数的实例,create_app是由from app import create_app, db导入的。
这里有一个坑需要注意一下,from app import create_app, db中的app是我们创建的一个包,其放置于顶层文件夹中,其中包括一个__init__.py文件。而语句app = create_app(os.getenv (‘FLASK_CONFIG’) or ‘default’)的app则是给create_app函数创建的实例的一个命名,这两个的含义是不一样的。对于我这种小白,刚刚开始也是迷惑了一阵子。
现在我们来看一下具体实现的方法。
os.getenv(‘FLASK_CONFIG’) or ‘default’),这个’FLASK_CONFIG’我们是我们设置的环境变量,通过os.getenv我们可以获得这个环境参数。因为这是对工厂函数create_app进行实例化,因此我们得到的环境参数就作为create_app的参数,如果没有设置’FLASK_CONFIG’这个环境变量,那么就将’default’这个默认值赋给工厂函数。那么这个设置的’FLASK_CONFIG’环境变量或这个’default’到底是什么呢?这需要到工厂函数中看一下。
既然create_app是从app包中导入的,那么让我们一起来看看这个包里面都有什么吧!打开app下面的__init__.py文件,看到没有create_app藏在这里呢。

此处插播工厂函数广告,为不影响文章的总体逻辑,特做分割

app / __ init __.py

from flask import Flask
from flask_bootstrap import Bootstrap
from flask_mail import Mail
from flask_moment import Moment
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_pagedown import PageDown
from config import config
#依旧是各种导入,这里应该不难理解,在工厂函数中导入了flask及其扩展,也通过from config import config导入了程序配置文件config.py中的config属性 bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()
pagedown = PageDown() login_manager = LoginManager()
login_manager.session_protection = 'strong'
login_manager.login_view = 'auth.login'
#这里是创建导入的Flask扩展的实例。 def create_app(config_name):
app = Flask(__name__) #创建Flask的实例
app.config.from_object(config[config_name])
config[config_name].init_app(app) bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
login_manager.init_app(app)
pagedown.init_app(app)
#创建flask扩展的实例 if not app.debug and not app.testing and not app.config['SSL_DISABLE']:
from flask_sslify import SSLify
sslify = SSLify(app) from .main import main as main_blueprint
app.register_blueprint(main_blueprint) from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth') from .api_1_0 import api as api_1_0_blueprint
app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')
#注册蓝图到工厂函数中 return app

广告结束,继续manage.py

我们来分析

app = create_app(os.getenv ('FLASK_CONFIG') or 'default')

在create_app中有如下的语句:

def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])

这里的os.getenv (‘FLASK_CONFIG’) or 'default’作为参数被传递给app.config.from_object(config[config_name]),其中的app是Flask的一个实例。

又一个坑:app.config中的config是Flask类的一个属性,与我们的配置文件config.py不是同一个东西,也不是config.py中的config属性。而config[config_name]得config是配置文件config.py中导入的,我们在config.py文件中看一下:

config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'heroku': HerokuConfig,
'unix': UnixConfig, 'default': DevelopmentConfig
}

可以发现,FLASK_CONFIG应该被设置成字典的键,根据你需要运行的模式选取’development’, ‘testing’, ‘production’, ‘heroku’, ‘unix’, ‘default’中的一个。当然,如果不在环境变量中设置的话,就默认选取的’default’。那么from_object返回的是该健对应值。例如设置’default’返回的就是’DevelopmentConfig’。
到了这里我们知道app.config.from_object(config[config_name])实际上就是app.config.DevelopmentConfig,这个config.DevelopmentConfig就是config.py中的DevelopmentConfig类。那么看看这个DevelopmentConfig:

class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')

这个DevelopmentConfig的父类是Config类,继承Config中的所有属性和方法。这样app.config.DevelopmentConfig就将DevelopmentConfig设定的配置绑定到了app——即Flask的实例上去。

当然,我们这里是以DevelopmentConfig为例,其他的配置是一样的原理。
接着执行create_app中的语句是:

config[config_name].init_app(app)

这里的init_app是在DevelopmentConfig类或其他运行环境配置类中的一个初始化方法,但值得注意的是,DevelopmentConfig类中并没有直接写入这个方法,而是继承Config类。
在config类中有一个静态方法,不执行任何操作。

@staticmethod
def init_app(app):
pass

因此在DevelopmentConfig中,这个初始化实际上并没有进行,直接pass。这是因为这里根本不需要初始化,之前执行的配置就完全够了。
但是值得注意的是,在ProductionConfig中是有重写这个方法,因此会根据重写的方法对app进行初始化配置。

bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
login_manager.init_app(app)
pagedown.init_app(app)

这时我们已经完成了实例化Flask,并对实例app进行了初始化配置。对于我们用到的flask扩展也进行同样的初始化配置。
坑:此处的init_app方法是各扩展中自带的方法,并不是刚才我们用到的在config.py中自己写的init_app方法,不过功能差不太多。但有时候让人挺迷惑的。

if not app.debug and not app.testing and not app.config['SSL_DISABLE']:
from flask_sslify import SSLify
sslify = SSLify(app)

这里是启用安全HTTP的flask扩展flask_sslify,先略去。

from .main import main as main_blueprint
app.register_blueprint(main_blueprint) from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix='/auth') from .api_1_0 import api as api_1_0_blueprint
app.register_blueprint(api_1_0_blueprint, url_prefix='/api/v1.0')

刚才说了那么多,无非是完成了实例化和配置初始化,但是我们的对网页处理的视图函数以及网页本身的内容在哪里处理呢?
视图函数我们是交给蓝本来处理的,此处是将蓝本注册到工厂函数中去。
到此有没有发现,在manage.py中大部分事情都让工厂函数干去了,首先是创建实例,然后初始化配置,最后把工作交给蓝本去干。
根据实现的功能不同分成不同的蓝本。比如在这个程序中,认证就放在auth这个蓝本中。蓝本去分配其中的路由。通过蓝本中的视图文件去操作form和template,对数据库的操作也是在视图文件中实现。原理就是这个原理,细节就不多说了。
接下来的代码:

manager = Manager(app)
migrate = Migrate(app, db)

这是方便程序在脚本下操作的flask_script中的Manage类的实例和数据库迁移的实例。
可能大家还有一个疑问:数据库是什么时候创建的,这其实在对工厂函数初始化配置的时候,通过调用config.py中的配置已经完成了。

总结

通过运行manage.py的过程分析,发现总体框架特点如下:
manage.py——公司的总经理
工厂函数——项目经理
config.py——公司财务总监
蓝本——各技术部门经理
视图文件——广大苦逼工程师
template/form——生产工具(枪/炮)

总经理:干
项目经理:好, 财务总监,这粮和钱…, 部门经理们,我给你们说个事呗
财务总监:有的,你有我有全都有。都在这里,别客气
技术部门经理:好的。那啥,小明,小红,抄家伙
工程师:…(端着枪扛着炮就冲出去了)

本文转自:https://blog.csdn.net/weixin_40834585/article/details/83042179

文章编写不易,实属感谢!

  

Flask架构管理及特点(重要)的更多相关文章

  1. Flask上下文管理、session原理和全局g对象

    一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...

  2. DCOS :私有云的物理基础架构管理引擎

    https://cloud.tencent.com/developer/article/1005598 一.引言 云计算经过多年的发展,逐渐从概念到渐为人认知.到接受.到现在全行业拥抱上云,云的客户也 ...

  3. 关于vue项目管理项目的架构管理平台

    关于vue项目管理项目的架构管理平台 https://panjiachen.github.io/vue-element-admin-site/#/zh-cn/faq 31.4k 次浏览 完整项目地址: ...

  4. Flask上下文管理

    一.一些python的知识 1.偏函数 def add(x, y, z): print(x + y + z) # 原本的写法:x,y,z可以传任意数字 add(1,2,3) # 如果我要实现一个功能, ...

  5. Active Directory架构管理

    Active Directory由对象(用户,计算机,打印机,组等)及其属性(属性)组成.Schema 是Active Directory的重要组件,因为它定义了用于存储数据的所有对象和属性.Acti ...

  6. Flask上下文管理机制

    前引 在了解flask上下文管理机制之前,先来一波必知必会的知识点. 面向对象双下方法 首先,先来聊一聊面向对象中的一些特殊的双下划线方法,比如__call__.__getattr__系列.__get ...

  7. flask上下管理文相关 - 总结

    flask上下管理文相关 - 总结 flask上下文管理机制 当用户请求到来之后,flask内部会创建两个对象: ctx = ReqeustContext(),内部封装request/sesion a ...

  8. Flask 上下文管理

    为什么用threading.local? 我们都知道线程是由进程创建出来的,CPU实际执行的也是线程,那么线程其实是没有自己独有的内存空间的,所有的线程共享进程的资源和空间,共享就会有冲突,对于多线程 ...

  9. Python学习笔记第二十三周(Flask架构)

    目录: 一.变量引用 内容: 备注:PyCharm小技巧,comm+alt+l  自动修改格式,comm+alt+return  向上添加新行 一.变量引用 1.url生成 from flask im ...

随机推荐

  1. B. Split a Number(字符串加法)

    Dima worked all day and wrote down on a long paper strip his favorite number nn consisting of ll dig ...

  2. Qt 信息提示框 QMessageBox

    information QMessageBox::information(NULL, "Title","Content",QMessageBox::Yes | ...

  3. Contiguous Repainting

    题目描述 There are N squares aligned in a row. The i-th square from the left contains an integer ai. Ini ...

  4. 基于TCP的大文件发送、UDP、socketserver

    基于TCP的大文件发送 #server服务端 import struct import json import os import socket server = socket.socket() # ...

  5. 量化预测质量之分类报告 sklearn.metrics.classification_report

    classification_report的调用为:classification_report(y_true, y_pred, labels=None, target_names=None, samp ...

  6. 三:mysql条件查询

    1:查询工资等于5000的员工

  7. go proxy转发工作中碰到的问题

    A-B 需求是一个中转 A-Proxy-B 读取来源请求A,在proxy读取body作些处理,再转给B,再把返回内容转给A 问题出在proxy这里 如果先把请求给B,再读body res, err : ...

  8. SHELL用法六(Find语句)

    1.SHELL编程Find语句案例实战 1)SHELL编程四剑客工具:Find.Grep.Sed.Awk,通过四剑客可以完成常规Linux指令无法完成或者比较复杂的功能,学好SHELL编程四剑客有助于 ...

  9. linux下的时区修改

    Centos 7时区问题: 通常使用tzselect命令选择时区,今天在修改centos7的时区的时候,修改完以后时区还是没有发生变化,重启也是没有用的:通过网络的帮助了解到,在Centos和ubun ...

  10. iText 中文无法显示

    /** * 导出PDF工具com.lowagie.itext测试 * * @param response * @throws IOException * @throws DocumentExcepti ...