python 全棧開發,Day142(flask標准目錄結構, flask使用SQLAlchemy,flask離線腳本,flask多app應用,flask-script,flask-migrate,pipreqs)


昨日內容回顧

1. 簡述flask上下文管理
    - threading.local
    - 偏函數
    -2. 原生SQL和ORM有什么優缺點?
    
    開發效率: ORM > 原生SQL
    執行效率: 原生SQL> ORM 
    
    如:SQLAlchemy依賴pymysql 

3. SQLAlchemy多線程連接的情況
View Code

 

一、flask標准目錄結構

標准flask目錄結構

Project name/  # 項目名
├── Project name  # 應用名,保持和項目名同名
│   ├── __init__.py  # 初始化程序,用來注冊藍圖
│   ├── static  # 靜態目錄
│   ├── templates  # 模板目錄
│   └── views  # 藍圖
└── manage.py  # 啟動程序

注意:應用名和項目名要保持一致

 

藍圖

修改manage.py

from lastday import create_app

app = create_app()

if __name__ == '__main__':
    app.run()

 

進入views目錄,新建文件account.py

from flask import Blueprint

account = Blueprint('account',__name__)

@account.route('/login')
def login():
    return '登陸'


@account.route('/logout')
def logout():
    return '注銷'
View Code

 

修改 __init__.py,注冊藍圖

from flask import Flask
from lastday.views.account import ac

def create_app():
    """
    創建app應用
    :return:
    """
    app = Flask(__name__)

    app.register_blueprint(ac)

    return app
View Code

 

執行manage.py,訪問首頁: http://127.0.0.1:5000/login

效果如下:

 

進入views目錄,新建文件user.py

from flask import Blueprint,render_template

us = Blueprint('user',__name__)  # 藍圖名

@us.route('/user_list/')
def user_list():  # 注意:不要和藍圖名重復
    return "用戶列表"

 

修改 __init__.py,注冊藍圖

from flask import Flask

from lastday.views.account import ac
from lastday.views.user import us

def create_app():
    """
    創建app應用
    :return:
    """
    app = Flask(__name__)

    # 注冊藍圖
    app.register_blueprint(ac)
    app.register_blueprint(us)

    return app
View Code

 

進入templates目錄,創建文件user_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
    <h1>用戶列表</h1>
</body>
</html>
View Code

 

修改views-->user.py,渲染模板

from flask import Blueprint,render_template

us = Blueprint('user',__name__)  # 藍圖名

@us.route('/user_list/')
def user_list():  # 注意:不要和藍圖名重復
    return render_template('user_list.html')
View Code

 

重啟manage.py,訪問用戶列表

 

在static目錄創建images文件夾,放一個圖片meinv.jpg

修改 templates\user_list.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
    <h1>用戶列表</h1>
    <div>橋本環奈</div>
    <img src="/static/images/meinv.jpg">
</body>
</html>
View Code

 

刷新頁面,效果如下:

 

如果使用藍圖,上面就是官方推薦的寫法!

 

配置文件

在項目根目錄,創建settings.py

class Base(object):
    SECRET_KEY = "fasdfasdf"  # session加密字符串

class Product(Base):
    """
    線上環境
    """
    pass

class Testing(Base):
    """
    測試環境
    """
    DEBUG = False

class Development(Base):
    """
    開發環境
    """
    DEBUG = True  # 開啟調試
View Code

這里的Base表示3個環境相同的配置

為什么配置文件要用類呢?待會再說

 

引入配置(推薦寫法)

from flask import Flask

from lastday.views.account import ac
from lastday.views.user import us

def create_app():
    """
    創建app應用
    :return:
    """
    app = Flask(__name__)

    # 引入配置文件並應用
    app.config.from_object("settings.Development")

    # 注冊藍圖
    app.register_blueprint(ac)
    app.register_blueprint(us)

    return app
View Code

配置文件使用類之后,如果要切換環境,這里改一下,就可以了!

那么類的靜態屬性,就是配置!

 

路由都寫在藍圖里面了,如果要對所有請求,做一下操作,怎么辦?

加before_request,要在哪里加?

在__init__.py里面加

from flask import Flask

from lastday.views.account import ac
from lastday.views.user import us

def create_app():
    """
    創建app應用
    :return:
    """
    app = Flask(__name__)

    # 引入配置文件並應用
    app.config.from_object("settings.Development")

    # 注冊藍圖
    app.register_blueprint(ac)
    app.register_blueprint(us)
    
    @app.before_request
    def b1():
        print('b1')

    return app
View Code

 

重啟manage.py,刷新頁面,查看Pycharm控制台輸出:b1

 

那么問題來了,如果b1的視圖函數,代碼有很多行呢?

在create_app里面有一個b1函數。b1函數就是一個閉包!

先來理解一下裝飾器的本質,比如b1函數,加了裝飾器之后,可以理解為:

b1 = app.before_request(b1)

 

由於賦值操作沒有用到,代碼縮減為

app.before_request(b1)

 

那么完整代碼,就可以寫成

from flask import Flask

from lastday.views.account import ac
from lastday.views.user import us

def create_app():
    """
    創建app應用
    :return:
    """
    app = Flask(__name__)

    # 引入配置文件並應用
    app.config.from_object("settings.Development")

    # 注冊藍圖
    app.register_blueprint(ac)
    app.register_blueprint(us)

    app.before_request(b1)  # 請求到來之前執行

    return app


def b1():
    print('app_b1')
View Code

 

其實藍圖,也可以加before_request

修改 views-->account.py

from flask import Blueprint

ac = Blueprint('account',__name__)

@ac.before_request
def bb():
    print('account.bb')


@ac.route('/login')
def login():
    return '登陸'


@ac.route('/logout')
def logout():
    return '注銷'
View Code

重啟 manage.py,訪問登錄頁面,注意:后面不要帶"/",否則提示Not Found

查看Pycharm控制台輸出:

app_b1
account.bb

 

可以發現,2個before_request都執行了。注意:在__init__.py中的before_request是所有路由都生效的

而account.py中的before_request,只要訪問這個藍圖的路由,就會觸發!

因此,訪問 http://127.0.0.1:5000/logout,也是可以觸發account.py中的before_request

 

完整目錄結構如下:

lastday/
├── lastday
│   ├── __init__.py
│   ├── static
│   │   └── images
│   │       └── meinv.jpg
│   ├── templates
│   │   └── user_list.html
│   └── views
│       ├── account.py
│       └── user.py
├── manage.py
└── settings.py

 

總結:

如果對所有的路由要操作,那么在app實例里面,寫before_request
如果對單個的藍圖,則在藍圖里面使用before_request

 

二、flask使用SQLAlchemy

必須先安裝模塊sqlalchemy

pip3 install sqlalchemy

准備一台MySQL服務器,創建數據庫db1

CREATE DATABASE db1 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

默認的用戶名為root,密碼為空

 

非主流操作

基於上面的項目lastday,進入lastday應用目錄,創建文件models.py

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, Index
from sqlalchemy.orm import relationship


# 創建了一個基類:models.Model
Base = declarative_base()

# 在數據庫創建表一張表
class Users(Base):
    __tablename__ = 'users'

    id = Column(Integer, primary_key=True)
    name = Column(String(32), index=True, nullable=False)



# 在數據庫創建表一張表
class School(Base):
    __tablename__ = 'school'

    id = Column(Integer, primary_key=True)
    name = Column(String(32), index=True, nullable=False)


def db_init():
    from sqlalchemy import create_engine
    # 創建數據庫連接
    engine = create_engine(
            # 連接數據庫db1
            "mysql+pymysql://root:@127.0.0.1:3306/db1?charset=utf8",
            max_overflow=0,  # 超過連接池大小外最多創建的連接
            pool_size=5,  # 連接池大小
            pool_timeout=30,  # 池中沒有線程最多等待的時間,否則報錯
            pool_recycle=-1  # 多久之后對線程池中的線程進行一次連接的回收(重置)
        )

    Base.metadata.create_all(engine)  # 創建操作
    # Base.metadata.drop_all(engine)  # 刪除操作

if __name__ == '__main__':

    db_init()
View Code

此時目錄結構如下:

lastday/
├── lastday
│   ├── __init__.py
│   ├── models.py
│   ├── static
│   │   └── images
│   │       └── meinv.jpg
│   ├── templates
│   │   └── user_list.html
│   └── views
│       ├── account.py
│       └── user.py
├── manage.py
└── settings.py

 

執行models.py,注意:它會輸出一段警告

Warning: (1366, "Incorrect string value: '\\xD6\\xD0\\xB9\\xFA\\xB1\\xEA...' for column 'VARIABLE_VALUE' at row 484")
  result = self._query(query)

這個異常是mysql問題,而非python的問題,這是因為mysql的字段類型是utf-xxx, 而在mysql中這些utf-8數據類型只能存儲最多三個字節的字符,而存不了包含四個字節的字符。

 

這個警告,可以直接忽略,使用Navicat軟件查看,發現表已經創建完成

 

修改 user.py,插入一條記錄

from flask import Blueprint,render_template
from lastday.models import Users


us = Blueprint('user',__name__)  # 藍圖名

@us.route('/user_list/')
def user_list():  # 注意:不要和藍圖名重復
    # 創建連接
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker
    engine = create_engine("mysql+pymysql://root:@127.0.0.1:3306/s11day139?charset=utf8", max_overflow=0, pool_size=5)
    Session = sessionmaker(bind=engine)
    session = Session()
    
    # 添加一條記錄
    session.add_all([
        Users(name='xiao')
    ])
    session.commit()
    
    return render_template('user_list.html')
View Code

可以發現,這種操作很麻煩。視圖函數每次都需要創建mysql連接!

 

使用flask_sqlalchemy(推薦)

安裝模塊flask_sqlalchemy

pip3 install flask_sqlalchemy

 

修改__init__.py,實例化SQLAlchemy,執行db.init_app(app)

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

# 1. 必須放在引入藍圖的上方
db = SQLAlchemy()

from lastday.views.account import ac
from lastday.views.user import us



def create_app():
    """
    創建app應用
    :return:
    """
    app = Flask(__name__)

    # 引入配置文件並應用
    app.config.from_object("settings.Development")

    # 2. 執行init_app,讀取配置文件SQLAlchemy中相關的配置文件,用於以后:生成數據庫/操作數據庫(依賴配置文件)
    db.init_app(app)

    # 注冊藍圖
    app.register_blueprint(ac)
    app.register_blueprint(us)

    app.before_request(b1)  # 請求到來之前執行

    return app

def b1():
    print('app_b1')
View Code

 

查看init_app的源碼,大量用到了app.config.setdefault

def init_app(self, app):
    """This callback can be used to initialize an application for the
    use with this database setup.  Never use a database in the context
    of an application not initialized that way or connections will
    leak.
    """
    if (
        'SQLALCHEMY_DATABASE_URI' not in app.config and
        'SQLALCHEMY_BINDS' not in app.config
    ):
        warnings.warn(
            'Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set. '
            'Defaulting SQLALCHEMY_DATABASE_URI to "sqlite:///:memory:".'
        )

    app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:')
    app.config.setdefault('SQLALCHEMY_BINDS', None)
    app.config.setdefault('SQLALCHEMY_NATIVE_UNICODE', None)
    app.config.setdefault('SQLALCHEMY_ECHO', False)
    app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', None)
    app.config.setdefault('SQLALCHEMY_POOL_SIZE', None)
    app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None)
    app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None)
    app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None)
    app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False)
    track_modifications = app.config.setdefault(
        'SQLALCHEMY_TRACK_MODIFICATIONS', None
    )
View Code

 

那么就需要將數據庫屬性,寫到settings.py中

class Base(object):
    SECRET_KEY = "fasdfasdf"  # session加密字符串

    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@127.0.0.1:3306/db1?charset=utf8"
    SQLALCHEMY_POOL_SIZE = 5
    SQLALCHEMY_POOL_TIMEOUT = 30
    SQLALCHEMY_POOL_RECYCLE = -1
    # 追蹤對象的修改並且發送信號
    SQLALCHEMY_TRACK_MODIFICATIONS = False

class Product(Base):
    """
    線上環境
    """
    pass

class Testing(Base):
    """
    測試環境
    """
    DEBUG = False

class Development(Base):
    """
    開發環境
    """
    DEBUG = True  # 開啟調試
View Code

 

因此,只要執行了db.init_app(app),它就會讀取settings.py中的配置信息

 

修改models.py,引入__init__.py中的db變量,優化代碼

from lastday import db


# 在數據庫創建表一張表
class Users(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), index=True, nullable=False)


# 在數據庫創建表一張表
class School(db.Model):
    __tablename__ = 'school'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), index=True, nullable=False)
View Code

 

修改 views-->user.py,導入db,插入一條記錄

from flask import Blueprint,render_template
from lastday.models import Users
from lastday import db

us = Blueprint('user',__name__)  # 藍圖名us

@us.route('/user_list/')
def user_list():  # 注意:不要和藍圖名重復
    # 添加一條記錄
    db.session.add(Users(name='xiao'))
    db.session.commit()

    return render_template('user_list.html')
View Code

 

重啟 manage.py ,訪問用戶列表,使用Navicat查看用戶表

發現多了一條記錄

 

如果需要關閉連接,使用 db.session.remove()

修改 views-->user.py,在commit下面加一行即可!

但是這樣沒有必要!為什么?因為在settings.py,使用了數據庫連接池。

關閉之后,再次開啟一個線程,是需要消耗cpu的。

 

三、flask離線腳本

前戲

流程圖

注意:核心就是配置,通過db對象,操作models,為藍圖提供數據!

 

現在有一個需求,需要將數據庫中的表刪除或者生成數據庫中的表,必須通過腳本來完成!

配置文件加載之后,將setttings.py中的屬性添加到app.config對象中。如果有app對象,那么就可以得到以下信息:

- 應用上下文中的有:app/g 
- flask的配置文件:app.config中 
- app中包含了:SQLAlchemy相關的數據。

web runtime

啟動網站,等待用戶請求到來。走 __call__/wsig_app/........

注意:上面這些,必須是flask啟動的情況下,才能獲取。有一個專有名詞,叫做web runtime,翻譯過來就是:web 運行時!

它是一個web運行狀態。某些操作,必須基於它才能實現!

離線腳本

離線腳本,就是非 web 運行時(web服務器停止)的狀態下,也能執行的腳本!

 

正式開始

先關閉flask項目

 

在項目根目錄創建文件table.py,導入create_app和db,這是關鍵點

from lastday import create_app,db

app = create_app()
with app.app_context():
    db.drop_all()  # 刪除

執行彈出一個警告,這不用管。

 

查看db1數據庫,發現表已經沒有了!

 

修改table.py,執行創建方法

from lastday import create_app,db

app = create_app()
with app.app_context():
    # db.drop_all()  # 刪除
    db.create_all()  # 創建

再次執行,發現表出現了

注意:網站並沒有啟動,但是實現了刪表以及創建表操作!

 

那么這個with,到底執行了什么呢?查看AppContext源代碼,看這2個方法

def __enter__(self):
    self.push()
    return self

def __exit__(self, exc_type, exc_value, tb):
    self.pop(exc_value)

    if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
        reraise(exc_type, exc_value, tb)

實際上,with是調用了這2個方法。

 

寫一個類測試一下

class Foo(object):
    pass

obj = Foo()
with obj:
    print(123)

執行報錯:

AttributeError: __enter__

提示沒有__enter__方法

 

修改代碼,增加__enter__方法

class Foo(object):
    def __enter__(self):
        pass

obj = Foo()
with obj:
    print(123)

執行報錯:

AttributeError: __exit__

 

再增加__exit__方法

class Foo(object):
    def __enter__(self):
        print('__enter__')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('__exit__')

obj = Foo()
with obj:
    print(123)

執行輸出:

__enter__
123
__exit__

也就是說,在執行print(123)之前,先執行了__enter__方法。之后,執行了__exit__。

之前學習的python基礎中,打開文件操作,使用了with open方法,也是一個道理!

在__enter__執行了打開文件句柄操作,在__exit__執行了關閉文件句柄操作!

 

 總結:

以后寫flask,要么是web運行時,要么是離線腳本。

應用場景

1. 夜間定時操作數據庫的表時

2. 數據導入。比如網站的三級聯動功能,將網上下載的全國城市.txt文件,使用腳本導入到數據庫中。

     還有敏感詞,網上都有現成的。下載txt文件后,導入到數據庫中。限制某些用戶不能發送敏感詞的內容!

3. 數據庫初始化,比如表的創建,索引創建等等

4. 銀行信用卡,到指定月的日期還款提醒。使用腳本將用戶的還款日期遍歷處理,給用戶發送一個短信提醒。每天執行一次!

5. 新寫一個項目時,數據庫沒有數據。用戶第一次使用時,無法直接看效果。寫一個腳本,自動錄入示例數據,方便用戶觀看!

 

四、flask多app應用

flask支持多個app應用,那么它們之間,是如何區分的呢?

是根據url前綴來區分,django多app也是通過url前綴來區分的。

 

由於url都在藍圖中,為藍圖加前綴,使用url_prefix。

語法:

xxx = Blueprint('account',__name__,url_prefix='/xxx')

 

修改 views-->account.py,增加前綴

from flask import Blueprint

ac = Blueprint('account',__name__,url_prefix='/xxx')

@ac.before_request
def bb():
    print('account.bb')


@ac.route('/login')
def login():
    return '登陸'


@ac.route('/logout')
def logout():
    return '注銷'
View Code

也可以在__init__.py里面的app.register_blueprint里面加url_prefix,但是不推薦

 

在項目根目錄創建目錄other,在里面創建 multi_app.py,不能使用__name__

from flask import Flask

app1 = Flask('app1')
# app1.config.from_object('xxx')  # db1
@app1.route('/f1')
def f1():
    return 'f1'

app2 = Flask('app2')
# app2.config.from_object('xxx')  # db2
@app1.route('/f2')
def f2():
    return 'f2'
View Code

上面2個應用,可以連接不同的數據庫。

目錄結構如下:

./
├── lastday
│   ├── __init__.py
│   ├── models.py
│   ├── static
│   │   └── images
│   │       └── meinv.jpg
│   ├── templates
│   │   └── user_list.html
│   └── views
│       ├── account.py
│       └── user.py
├── manage.py
├── other
│   └── multi_app.py
└── settings.py

 

multi_app.py有2套程序,沒有必要寫在一起,使用DispatcherMiddleware

查看源碼

class DispatcherMiddleware(object):

    """Allows one to mount middlewares or applications in a WSGI application.
    This is useful if you want to combine multiple WSGI applications::

        app = DispatcherMiddleware(app, {
            '/app2':        app2,
            '/app3':        app3
        })
    """

    def __init__(self, app, mounts=None):
        self.app = app
        self.mounts = mounts or {}

    def __call__(self, environ, start_response):
        script = environ.get('PATH_INFO', '')
        path_info = ''
        while '/' in script:
            if script in self.mounts:
                app = self.mounts[script]
                break
            script, last_item = script.rsplit('/', 1)
            path_info = '/%s%s' % (last_item, path_info)
        else:
            app = self.mounts.get(script, self.app)
        original_script_name = environ.get('SCRIPT_NAME', '')
        environ['SCRIPT_NAME'] = original_script_name + script
        environ['PATH_INFO'] = path_info
        return app(environ, start_response)
View Code

它可以將2個app結合在一起,使用run_simple啟動

 

修改 other\multi_app.py

from flask import Flask
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple

app1 = Flask('app1')
# app1.config.from_object('xxx') # db1

@app1.route('/f1')
def f1():
    return 'f1'


app2 = Flask('app2')
# app1.config.from_object('xxx') # db2
@app2.route('/f2')
def f2():
    return 'f2'


dispachcer = DispatcherMiddleware(app1, {
    '/admin':app2,  # app2指定前綴admin
})

if __name__ == '__main__':
    run_simple('127.0.0.1',8009,dispachcer)
View Code

 

執行multi_app.py,訪問url

http://127.0.0.1:8009/f1

效果如下:

 

訪問f2,會出現404

 

因為它規定了前綴 admin,使用下面訪問訪問,就不會出錯了!

 

多app應用的場景很少見,了解一下,就可以了!

 

維護棧的目的

 

在local對象中,存儲的數據是這樣的。app_ctx是應用上下文

線程id:{stack:[app_ctx]}

它永遠存儲的是單條數據,它不是真正的棧。如果搞一個字段,直接讓stack=app_ctx,照樣可以執行。

那么它為什么要維護一個棧呢?因為它要考慮:

在離線腳本和多app應用的情況下特殊代碼的實現。

只有這2個條件滿足的情況下,才會用到棧!

web運行時

看上圖,是web運行時。本質上,要么開多進程,要么開多線程。那么local對象中維護的棧,永遠都只有一條數據。

即使是多app應用,也是一樣的。一個請求過來,只能選擇一個app。比如上面的f1和f2,要么帶前綴,要么不帶。帶前綴,訪問f2,否則訪問f1

離線腳本

單app應用

在離線腳本中,單app應用,先來看table.py,它就是離線腳本。

from lastday import create_app,db

app = create_app()

# app_ctx.push()
with app.app_context():
    db.create_all()  # 創建

 

它創建了app_ctx對象,調用了push方法。將數據放到Local對象中,注意:只放了一次!

local的數據,如果是一個字典,大概是這個樣子

{
    stack:[app_ctx,]
}

 

多app應用

修改table.py,注意:下面的是偽代碼,直接運行會報錯

from lastday import create_app,db

app1 = create_app1()  # db1
app2 = create_app2()  # db2

# app_ctx.push()
"""
{
    stack:[app1_ctx,]
}
"""
with app1.app_context():
    # 取棧中獲取棧頂的app_ctx,使用top方法取棧頂
    db.create_all()  # 創建

如果app1要獲取配置文件,從db1種獲取

 

如果加一行代碼with呢?

from lastday import create_app,db
from flask import globals

app1 = create_app1()  # db1
app2 = create_app2()  # db2

# app_ctx.push()
"""
{
    stack:[app1_ctx,]
}
"""
with app1.app_context():
    # 取棧中獲取棧頂的app_ctx,使用top方法取棧頂
    db.create_all()  # 創建
    
    with app2.app_context():
View Code

它們都調用了app_context

 

看globals源碼,看最后一行代碼

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()

這2個靜態變量,用的是同一個LocalStack(),那么會用同一個Local()。也就是說放到同一個地方去了

 

修改table.py

from lastday import create_app,db
from flask import globals

app1 = create_app1()  # db1
app2 = create_app2()  # db2

# app_ctx.push()
"""
{
    stack:[app1_ctx,app2_ctx,]
}
"""
with app1.app_context():
    # 取棧中獲取棧頂的app_ctx,使用top方法取棧頂
    db.create_all()  # 創建

    with app2.app_context():
        db.create_all()
View Code

 

執行到with這一行時,stack里面有2個對象,分別是app1_ctx和app2_ctx。

那么執行到with下面的db.create_all()時,它會連接哪個數據庫?

答案是: 取棧頂的app2_ctx,配置文件是db2

 

修改 table.py,增加db.drop_all(),它會刪除哪個數據庫?

from lastday import create_app,db
from flask import globals

app1 = create_app1()  # db1
app2 = create_app2()  # db2

# app_ctx.push()
"""
{
    stack:[app1_ctx,app2_ctx,]
}
"""
with app1.app_context():
    # 取棧中獲取棧頂的app_ctx,使用top方法取棧頂
    db.create_all()  # 創建

    with app2.app_context():
        db.create_all()
        
    db.drop_all()
View Code

 

答案是:db1

 

為什么呢?因為執行with時,進來時調用了__enter__方法,將app2_ctx加進去了。此時位於棧頂!

結束時,調用__exit__方法,取棧頂,將app2_ctx給pop掉了!也就是刪除!

 

那么執行db.drop_all()時,此時棧里面只有一個數據app1_ctx,取棧頂,就是app1_ctx

這就它設計使用棧的牛逼之處!

通過棧頂的數據不一樣,來完成多app操作!

 

看下面的動態圖,就是棧的變化

 

關於flask維護棧的詳細信息,請參考鏈接:

https://blog.csdn.net/u010301542/article/details/78302450

 

五、flask_script

Flask Script擴展提供向Flask插入外部腳本的功能,包括運行一個開發用的服務器,一個定制的Python shell,設置數據庫的腳本,cronjobs,及其他運行在web應用之外的命令行任務;使得腳本和系統分開;

Flask Script和Flask本身的工作方式類似,只需定義和添加從命令行中被Manager實例調用的命令;

官方文檔:http://flask-script.readthedocs.io/en/latest/

 

安裝模塊

pip3 install flask_script

 

修改 manage.py

from flask_script import Manager
from lastday import create_app

app = create_app()
manager = Manager(app)

if __name__ == '__main__':
    manager.run()
View Code

執行 manage.py,報錯

optional arguments:
  -?, --help         show this help message and exit

是因為不能用原來的方式調用了。

 

使用以下方式執行

python manage.py runserver -h 127.0.0.1 -p 8009

 

訪問登錄頁面

 

自定義命令

flask_script還可以做一些自定義命令,列如:

修改 manage.py

from flask_script import Manager
from lastday import create_app

app = create_app()
manager = Manager(app)

@manager.command
def c1(arg):
    """
    自定義命令login
    python manage.py custom 123
    :param arg:
    :return:
    """
    print(arg)


@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def c2(name, url):
    """
    自定義命令
    執行: python manage.py  cmd -n wupeiqi -u http://www.oldboyedu.com
    :param name:
    :param url:
    :return:
    """
    print(name, url)


if __name__ == '__main__':
    manager.run()
View Code

 

在終端執行c1命令

python manage.py c1 22

執行輸出:22

 

注意:這里的c1,指的是manage.py中的c1函數

 

在終端執行c2命令

python manage.py c2 -n 1 -u 9

執行輸出:

1 9

 

以上,可以看到,它和django啟動方式很像

 

六、flask_migrate

flask-migrate是flask的一個擴展模塊,主要是擴展數據庫表結構的.

官方文檔:http://flask-migrate.readthedocs.io/en/latest/

 

安裝模塊

pip3 install flask_migrate

使用

修改 manage.py

# 1.1
from flask_script import Manager
# 2.1
from flask_migrate import Migrate, MigrateCommand


from lastday import db
from lastday import create_app


app = create_app()
# 1.2
manager = Manager(app)

# 2.2
Migrate(app, db)

# 2.3
"""
# 數據庫遷移命名
    python manage.py db init
    python manage.py db migrate  -> makemigrations
    python manage.py db upgrade  -> migrate
"""
manager.add_command('db', MigrateCommand)



@manager.command
def c1(arg):
    """
    自定義命令
    python manage.py custom 123
    :param arg:
    :return:
    """
    print(arg)


@manager.option('-n', '--name', dest='name')
@manager.option('-u', '--url', dest='url')
def c2(name, url):
    """
    自定義命令
    執行: python manage.py  cmd -n wupeiqi -u http://www.oldboyedu.com
    :param name:
    :param url:
    :return:
    """
    print(name, url)



if __name__ == '__main__':
    # python manage.py runserver -h 127.0.0.1 -p 8999
    # 1.3
    manager.run()
View Code

 

執行init

必須先執行init,只需要執行一次就可以了!

python manage.py db init

它會在項目根目錄創建migrations文件夾

 

執行migrate

python manage.py db migrate

 

執行upgrade

python manage.py db upgrade

 

測試

修改 models.py,去掉School中的name屬性

from lastday import db


# 在數據庫創建表一張表
class Users(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), index=True, nullable=False)


# 在數據庫創建表一張表
class School(db.Model):
    __tablename__ = 'school'

    id = db.Column(db.Integer, primary_key=True)
    # name = db.Column(db.String(32), index=True, nullable=False)
View Code

 

先執行migrate,再執行upgrade

 

使用Navicat查看school表,發現name字段沒有了!

 

它是如何實現的呢?在migrations-->versions目錄里面,有一個xx.py,它記錄的models.py的修改。

那么它和django也是同樣,有一個文件記錄變化。

 

那么因此,當flask的插件越裝越多時,它和django是一樣的

 

七、pipreqs 

介紹

pipreqs可以通過對項目目錄的掃描,自動發現使用了那些類庫,自動生成依賴清單。缺點是可能會有些偏差,需要檢查並自己調整下。

 

假設一個場景:小a剛去新公司入職。領導讓它做2件事情,1. 安裝python環境,安裝django 2. 看項目代碼,一周之后,寫需求。

安裝django之后,運行項目代碼報錯了,提示沒有安裝xx模塊!

然后默默的安裝了一下xx模塊,再次運行,又報錯了。再安裝....,最后發現安裝了30個安裝包!

最后再運行,發現還是報錯了!不是xx模塊問題,是xx語法報錯!

這個時候問領導,這些模塊,都是什么版本啊?

 

一般代碼上線,交給運維。你要告訴它,這個項目,需要安裝xx模塊,版本是多少。寫一個文件,甩給運維!這樣太麻煩了!

為了避免上述問題,出現了pipreps模塊,它的作用是: 自動找到程序中應用的包和版本

 

安裝模塊

pip3 install pipreqs

使用pipreqs

注意:由於windows默認是gbk編碼,必須指定編碼為utf-8,否則報錯!

E:\python_script\Flask框架\day5\lastday> pipreqs ./ --encoding=utf-8

執行輸出:

INFO: Successfully saved requirements file in E:\python_script\Flask框架\day5\lastday\requirements.txt

它會在當前目錄中,生成一個requirements.txt文件

查看文件內容

Flask_SQLAlchemy==2.3.2
Flask==1.0.2

左邊是模塊名,右邊是版本

 

那么有了這個requirements.txt文件,就可以自動安裝模塊了

pip3 install -r requirements.txt

它會根據文件內容,自動安裝!

 

因此,寫python項目時,一定要有requirements.txt文件才行!

github項目也是一樣的!

 

今日內容總結:

內容詳細:

    1. flask & SQLAlchemy 
        
        安裝:
            flask-sqlalchemy
            
        使用:
            a. 在配置文件中設置連接字符串
                    SQLALCHEMY_DATABASE_URI = "mysql+pymysql://root:@127.0.0.1:3306/s11lastday?charset=utf8"
                    SQLALCHEMY_POOL_SIZE = 5
                    SQLALCHEMY_POOL_TIMEOUT = 30
                    SQLALCHEMY_POOL_RECYCLE = -1
                    # 追蹤對象的修改並且發送信號
                    SQLALCHEMY_TRACK_MODIFICATIONS = False
            b. __init__.py 
                from flask import Flask
                from flask_sqlalchemy import SQLAlchemy

                # 1. 必須放在引入藍圖的上方
                db = SQLAlchemy()

                from lastday.views.user import user

                def create_app():
                    app = Flask(__name__)
                    app.config.from_object("settings.Development")

                    
                    # 2. 執行init_app,讀取配置文件SQLAlchemy中相關的配置文件,用於以后:生成數據庫/操作數據庫(依賴配置文件)
                    db.init_app(app)

                    app.register_blueprint(user)

                    return app
            c. models.py 
                from lastday import db


                # 在數據庫創建表一張表
                class Users(db.Model):
                    __tablename__ = 'users'

                    id = db.Column(db.Integer, primary_key=True)
                    name = db.Column(db.String(32), index=True, nullable=False)


                # 在數據庫創建表一張表
                class School(db.Model):
                    __tablename__ = 'school'

                    id = db.Column(db.Integer, primary_key=True)
                    name = db.Column(db.String(32), index=True, nullable=False)

            d. 藍圖
                    from flask import Blueprint,render_template
                    from lastday.models import Users
                    from lastday import db

                    user = Blueprint('user',__name__)

                    @user.route('/user_list/')
                    def user_list():

                        db.session.add(Users(name='王超'))
                        db.session.commit()
                        db.session.remove()


                        return render_template('user_list.html')

        疑問:將數據庫中的表刪除 或 生成數據庫中的表。
            
            通過腳本完成。
                前戲:
                    - 應用上下文中的有:app/g 
                    - flask的配置文件:app.config中 
                    - app中包含了:SQLAlchemy相關的數據。
                代碼:
                    from lastday import create_app,db
                    app = create_app()

                    with app.app_context():
                        
                        # db.drop_all()
                        db.create_all()

        
        名詞:
            web runtime :啟動網站,等待用戶請求到來。走 __call__/wsig_app/........
            離線腳本:
                from lastday import create_app,db
                app = create_app()

                with app.app_context():
                    
                    # db.drop_all()
                    db.create_all()

                應用場景:
                    1. 錄入基礎數據
                    2. 定時數據處理(定時任務)
                    
        
        
        贈送:多app應用
            from flask import Flask
            from werkzeug.wsgi import DispatcherMiddleware
            from werkzeug.serving import run_simple

            app1 = Flask('app1')
            # app1.config.from_object('xxx') # db1

            @app1.route('/f1')
            def f1():
                return 'f1'


            app2 = Flask('app2')
            # app1.config.from_object('xxx') # db2
            @app2.route('/f2')
            def f2():
                return 'f2'


            dispachcer = DispatcherMiddleware(app1, {
                '/admin':app2,
            })

            if __name__ == '__main__':
                run_simple('127.0.0.1',8009,dispachcer)
            
            
            
        問題:為什么Flask中要在上下文管理中將Local中的數據:ctx/app_ctx維護成一個棧?
            
            應用flask要考慮,在離線腳本和多app應用的情況下特殊代碼的實現。
            
            在web運行時中:Local對象中維護的棧 [ctx, ]
            在離線腳本中:
                - 單app應用:
                    from lastday import create_app,db
                    app = create_app()

                    # app_ctx.push()
                    """
                    {
                        stack:[app_ctx, ]
                    }
                    """
                    with app.app_context():

                        db.create_all()
                - 多app應用:
                    from lastday import create_app,db
                    app1 = create_app1() # db1
                    app2 = create_app2() # db2
                    from flask import globals
                    # app_ctx.push()
                    """
                    {
                        stack:[app1_ctx,app2_ctx ]
                    }
                    """
                    with app1.app_context():

                        # 去棧中獲取棧頂的app_ctx: app1_ctx,配置文件:db1
                        db.create_all()

                        with app2.app_context():
                            db.create_all() # 去棧中獲取棧頂的app_ctx: app2_ctx,配置文件:db2

                        db.drop_all() # 去棧中獲取棧頂的app_ctx: app1_ctx,配置文件:db1
                                

        總結:
            1. flask-sqlalchemy,幫助用戶快速實現Flask中應用SQLAlchemy 
            
            2. 多app應用  
            
            3. 離線腳本 
            
            4. 為什么Flask中要在上下文管理中將Local中的數據:ctx/app_ctx維護成一個棧?
                - 離線腳本+多app應用才會在棧中存多個上下文對象: [ctx1,ctx2,]
                - 其他:[ctx, ]
        
    2. flask-script 
        
        安裝:flask-script
        
        作用:制作腳本啟動
    
    3. flask-migrate(依賴flask-script )
        安裝:flask-migrate
        
        使用:
            # 1.1
            from flask_script import Manager
            # 2.1
            from flask_migrate import Migrate, MigrateCommand


            from lastday import db
            from lastday import create_app


            app = create_app()
            # 1.2
            manager = Manager(app)

            # 2.2
            Migrate(app, db)

            # 2.3
            """
            # 數據庫遷移命名
                python manage.py db init
                python manage.py db migrate  -> makemigrations
                python manage.py db upgrade  -> migrate
            """
            manager.add_command('db', MigrateCommand)



            @manager.command
            def c1(arg):
                """
                自定義命令
                python manage.py custom 123
                :param arg:
                :return:
                """
                print(arg)


            @manager.option('-n', '--name', dest='name')
            @manager.option('-u', '--url', dest='url')
            def c2(name, url):
                """
                自定義命令
                執行: python manage.py  cmd -n wupeiqi -u http://www.oldboyedu.com
                :param name:
                :param url:
                :return:
                """
                print(name, url)



            if __name__ == '__main__':
                # python manage.py runserver -h 127.0.0.1 -p 8999
                # 1.3
                manager.run()
                
    4. pipreqs 
        安裝:pipreqs
        
        作用:自動找到程序中應用的包和版本。
            
            
            pipreqs ./ --encoding=utf-8
            
            
            pip3 install -r requirements.txt
        
        
重點:
    1. 使用(*- flask-sqlalchemy
        - flask-migrate
        - flask-script
    
    2. flask上下文相關 (*****- 對象關鍵字:LocalStack、Local 
        - 離線腳本 & web 運行時 
        - 多app應用 
        - Local中為什么維護成棧?
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM