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

昨日內容回顧

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

3. SQLAlchemy多線程鏈接的狀況
View Code

 

1、flask標準目錄結構

標準flask目錄結構

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

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

 

藍圖

修改manage.pypython

from lastday import create_app

app = create_app()

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

 

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

from flask import Blueprint

account = Blueprint('account',__name__)

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


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

 

修改 __init__.py,註冊藍圖git

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/logingithub

效果以下:web

 

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

from flask import Blueprint,render_template

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

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

 

修改 __init__.py,註冊藍圖shell

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,渲染模板django

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

 

2、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的。

 

3、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. 新寫一個項目時,數據庫沒有數據。用戶第一次使用時,沒法直接看效果。寫一個腳本,自動錄入示例數據,方便用戶觀看!

 

4、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維護棧的詳細信息,請參考連接:

http://www.javashuo.com/article/p-aexrsilv-ec.html

 

5、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啓動方式很像

 

6、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是同樣的

 

7、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
相關文章
相關標籤/搜索