flask 源碼 0.1 版本

閱讀flask源碼,可以先從flask最早的版本開始閱讀

flask最早發行的0.1版本只包含一個核心腳本flask.py(400多行)

源碼文檔來自:

(英文版)

https://github.com/pallets/flask

(greyli翻譯)

https://github.com/greyli/flask-origin


英文版:

    pycharm 將代碼克隆下來後使用 git checkout 0.1 可以檢出flask 0.1版本(英語好的同學可以看這個~


greyli翻譯:

    greyli 將0.1的源碼翻譯並製作了4個類型:

        mini:去除所有註釋和文檔字符串

        origin:原版

        translated:翻譯所有註釋和文檔字符串

        annotated:添加註解

    克隆下來後,可以根據自己需要checkout對應的版本。


話不多說,只搬運0.1代碼,詳細內容去來源文檔查看。

# -*- coding: utf-8 -*-
"""
    Flask-Origin
    ~~~~~~~~~~~~~
     
    Flask 0.1版本源碼註解。

    :author: Grey Li (李輝)
    :url: http://greyli.com
    :copyright: (c) 2018 Grey Li <[email protected]>
    :license: MIT, see LICENSE for more details.
"""
from __future__ import with_statement
import os
import sys

from threading import local
from jinja2 import Environment, PackageLoader, FileSystemLoader
from werkzeug import Request as RequestBase, Response as ResponseBase, \
     LocalStack, LocalProxy, create_environ, cached_property, \
     SharedDataMiddleware
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, InternalServerError
from werkzeug.contrib.securecookie import SecureCookie

# 這些從Werkzeug和Jinja2導入的輔助函數(utilities)沒有在
# 模塊內使用,而是直接作爲外部接口開放
from werkzeug import abort, redirect
from jinja2 import Markup, escape

# 優先使用pkg_resource,如果無法工作則使用cwd。
try:
    import pkg_resources
    pkg_resources.resource_stream
except (ImportError, AttributeError):
    pkg_resources = None


class Request(RequestBase):
    """Flask默認使用的請求對象,用來記住匹配的端點值(endpoint)和視圖參數(view arguments)。

    這就是最終的flask.request對象。如果你想替換掉這個請求對象,可以子類化這個
    類,然後將你的子類賦值給flask.Flask.request_class。
    """

    def __init__(self, environ):
        RequestBase.__init__(self, environ)
        self.endpoint = None
        self.view_args = None


class Response(ResponseBase):
    """Flask默認使用的響應對象。除了將MIME類型默認設置爲HTML外,和Werkzeug提供的響應對象
    完全相同。通常情況下,你不需要自己創建這個對象,因爲flask.Flask.make_response
    會負責這個工作。

    如果你想替換這個響應對象,你可以子類化這個類,然後將你的子類賦值給flask.Flask.request_class。
    """
    default_mimetype = 'text/html'


class _RequestGlobals(object):
    pass


class _RequestContext(object):
    """請求上下文(request context)包含所有請求相關的信息。它會在請求進入時被創建,
    然後被推送到_request_ctx_stack,在請求結束時會被相應的移除。它會爲提供的
    WSGI環境創建URL適配器(adapter)和請求對象。
    """

    def __init__(self, app, environ):
        self.app = app
        self.url_adapter = app.url_map.bind_to_environ(environ)
        self.request = app.request_class(environ)
        self.session = app.open_session(self.request)
        self.g = _RequestGlobals()
        self.flashes = None

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

    def __exit__(self, exc_type, exc_value, tb):
        # 在調試模式(debug mode)而且有異常發生時,不要移除(pop)請求堆棧。
        # 這將允許調試器(debugger)在交互式shell中仍然可以獲取請求對象。
        if tb is None or not self.app.debug:
            _request_ctx_stack.pop()


def url_for(endpoint, **values):
    """根據給定的端點和提供的方法生成一個URL。

    :param endpoint: URL的端點值(函數名)。
    :param values: URL規則的變量參數。
    """
    return _request_ctx_stack.top.url_adapter.build(endpoint, values)


def flash(message):
    """閃現(flash)一個消息到下一個請求。爲了從session中移除閃現過的消息
    並將其顯示給用戶,你必須在模板中調用get_flashed_messages。

    :param message: 被閃現的消息。
    """
    session['_flashes'] = (session.get('_flashes', [])) + [message]


def get_flashed_messages():
    """從session里拉取(pull)所有要閃現的消息並返回它們。在同一個請求中對這個函數的
    進一步調用會返回同樣的消息。
    """
    flashes = _request_ctx_stack.top.flashes
    if flashes is None:
        _request_ctx_stack.top.flashes = flashes = \
            session.pop('_flashes', [])
    return flashes


def render_template(template_name, **context):
    """使用給定的上下文從模板(template)文件夾渲染一個模板。
    
    :param template_name: 要被渲染的模板文件名。
    :param context: 在模板上下文中應該可用(available)的變量。
    """
    current_app.update_template_context(context)
    return current_app.jinja_env.get_template(template_name).render(context)


def render_template_string(source, **context):
    """使用給定的模板源代碼字符串(source string)和上下文渲染一個模板。

    :param template_name: 要被渲染的模板源代碼。
    :param context: 在模板上下文中應該可用的變量。
    """
    current_app.update_template_context(context)
    return current_app.jinja_env.from_string(source).render(context)


def _default_template_ctx_processor():
    """默認的模板上下文處理器(processor)。注入request、session和g。"""
    reqctx = _request_ctx_stack.top
    return dict(
        request=reqctx.request,
        session=reqctx.session,
        g=reqctx.g
    )


def _get_package_path(name):
    """返回包的路徑,如果找不到則返回當前工作目錄(cwd)。"""
    try:
        return os.path.abspath(os.path.dirname(sys.modules[name].__file__))
    except (KeyError, AttributeError):
        return os.getcwd()


class Flask(object):
    """這個flask對象實現了WSGI程序並作爲中心對象存在。傳入的參數(package_name)爲
    程序所在的模塊或包的名稱。一旦這個對象被創建,它將作爲一箇中心註冊處,所有的視圖
    函數、URL規則、模板配置等等都將註冊到這裏。

    包的名稱被用來從包的內部或模塊所在的文件夾解析資源,具體的位置取決於傳入的包名稱
    參數(package_name)指向一個真實的Python包(包含__init__.py文件的文件夾)
    還是一個標準的模塊(.py文件)。

    關於資源加載的更多信息,參見open_resource。

    通常,你會在你的主腳本或包中的__init__.py文件裏使用下面的方式創建一個Flask實例:

        from flask import Flask
        app = Flask(__name__)
    
    """

    #: 用作請求對象的類。更多信息參見flask.request。
    request_class = Request

    #: 用作響應對象的類。更多信息參見flask.Response。
    response_class = Response

    #: 靜態文件的路徑。如果你不想使用靜態文件,可以將這個值設爲None,這樣不會添加
    #: 相應的URL規則,而且開發服務器將不再提供(serve)任何靜態文件。
    static_path = '/static'

    #: 如果設置了密鑰(secret key),加密組件(即itsdangerous)可以使用它來爲
    #: cookies或其他東西簽名。比如,當你想使用安全的cookie時,把它設爲一個複雜的隨機值。
    secret_key = None

    #: 安全cookie使用這個值作爲session cookie的名稱。
    session_cookie_name = 'session'

    #: 直接傳入Jinja2環境的選項。
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

    def __init__(self, package_name):
        #: 調試標誌。將它設爲True來開啓調試模式。在調試模式下,當一個未捕捉
        #: 的異常觸發時,調試器會啓動;而且,當代碼中的變動被探測到時,開發
        #: 服務器會自動重載程序。
        self.debug = False

        #: 包或模塊的名稱。一旦它通過構造器設置後,就不要更改這個值。
        self.package_name = package_name

        #: 定位程序的根目錄。
        self.root_path = _get_package_path(self.package_name)

        #: 一個儲存所有已註冊的視圖函數的字典。字典的鍵將是函數的名稱,這些名稱
        #: 也被用來生成URL;字典的值是函數對象本身。
        #: 要註冊一個視圖函數,使用route裝飾器(decorator)。
        self.view_functions = {}

        #: 一個儲存所有已註冊的錯誤處理器的字典。字段的鍵是整型(integer)類型的
        #: 錯誤碼,字典的值是處理對應錯誤的函數。
        #: 要註冊一個錯誤處理器,使用errorhandler裝飾器。
        self.error_handlers = {}

        #: 一個應該在請求開始進入時、請求分發開始前調用的函數列表。舉例來說,
        #: 這可以用來打開數據庫連接或獲取當前登錄的用戶。
        #: 要註冊一個函數到這裏,使用before_request裝飾器。
        self.before_request_funcs = []

        #: 一個應該在請求處理結束時調用的函數列表。這些函數會被傳入當前的響應
        #: 對象,你可以在函數內修改或替換它。
        #: 要註冊一個函數到這裏,使用after_request裝飾器。
        self.after_request_funcs = []

        #: 一個將被無參數調用以生成模板上下文的的函數列表。每一個函數應返回一個
        #: 用於更新模板上下文的字典。
        #: 要註冊一個函數到這裏,使用context_processor裝飾器。
        self.template_context_processors = [_default_template_ctx_processor]

        self.url_map = Map()

        if self.static_path is not None:
            self.url_map.add(Rule(self.static_path + '/<filename>',
                                  build_only=True, endpoint='static'))
            if pkg_resources is not None:
                target = (self.package_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                self.static_path: target
            })

        #: Jinja2環境。它通過jinja_options創建,加載器(loader)通過
        #: create_jinja_loader函數返回。
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )

    def create_jinja_loader(self):
        """創建Jinja加載器。默認只是返回一個對應配置好的包的包加載器,它會從
        templates文件夾中尋找模板。要添加其他加載器,可以重載這個方法。
        """
        if pkg_resources is None:
            return FileSystemLoader(os.path.join(self.root_path, 'templates'))
        return PackageLoader(self.package_name)

    def update_template_context(self, context):
        """使用常用的變量更新模板上下文。這會注入request、session和g到模板上下文中。

        :param context: 包含額外添加的變量的字典,用來更新上下文。
        """
        reqctx = _request_ctx_stack.top
        for func in self.template_context_processors:
            context.update(func())

    def run(self, host='localhost', port=5000, **options):
        """在本地開發服務器上運行程序。如果debug標誌被設置,這個服務器
        會在代碼更改時自動重載,並會在異常發生時顯示一個調試器。
        
        :param host: 監聽的主機名。設爲'0.0.0.0'可以讓服務器外部可見。
        :param port: 服務器的端口。
        :param options: 這些選項將被轉發給底層的Werkzeug服務器。更多信息
                        參見werkzeug.run_simple。
        """
        from werkzeug import run_simple
        if 'debug' in options:
            self.debug = options.pop('debug')
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        return run_simple(host, port, self, **options)

    def test_client(self):
        """爲這個程序創建一個測試客戶端。"""
        from werkzeug import Client
        return Client(self, self.response_class, use_cookies=True)

    def open_resource(self, resource):
        """從程序的資源文件夾打開一個資源。至於它是如何工作的,考慮下面的文件
        目錄:

            /myapplication.py
            /schemal.sql
            /static
                /style.css
            /template
                /layout.html
                /index.html

        如果你想打開schema.sql文件,可以這樣做:

            with app.open_resource('schema.sql') as f:
                contents = f.read()
                do_something_with(contents)

        :param resource: 資源文件的名稱。要獲取子文件夾中的資源,使用斜線作爲分界符。
        """
        if pkg_resources is None:
            return open(os.path.join(self.root_path, resource), 'rb')
        return pkg_resources.resource_stream(self.package_name, resource)

    def open_session(self, request):
        """創建或打開一個新的session。默認的實現是存儲所有的用戶會話(session)
        數據到一個簽名的cookie中。這需要secret_key屬性被設置。

        :param request: request_class的實例。
        """
        key = self.secret_key
        if key is not None:
            return SecureCookie.load_cookie(request, self.session_cookie_name,
                                            secret_key=key)

    def save_session(self, session, response):
        """如果需要更新,保存session。默認實現參見open_session。
        
        :param session: 要被保存的session
                        (一個werkzeug.contrib.securecookie.SecureCookie對象)
        :param response: 一個response_class實例。
        """
        if session is not None:
            session.save_cookie(response, self.session_cookie_name)

    def add_url_rule(self, rule, endpoint, **options):
        """連接一個URL規則。效果和route裝飾器完全相同,不過不會爲端點註冊視圖函數。

        基本示例:

            @app.route('/')
            def index():
                pass

        和下面相同:

            def index():
                pass
            app.add_url_rule('index', '/')
            app.view_functions['index'] = index

        :param rule: 字符串形式的URL規則。
        :param endpoint: 對應被註冊的URL規則的端點。Flask默認將視圖函數名作爲端點。
        :param options: 轉發給底層的werkzeug.routing.Rule對象的選項。
        """
        options['endpoint'] = endpoint
        options.setdefault('methods', ('GET',))
        self.url_map.add(Rule(rule, **options))

    def route(self, rule, **options):
        """一個用於爲給定的URL規則註冊視圖函數的裝飾器。示例:

            @app.route('/')
            def index():
                return 'Hello World'

        路由中的變量部分可以使用尖括號來指定(/user/<username>)。默認情況下,
        URL中的變量部分接受任意不包含斜線的字符串,你也可以使用<converter:name>
        的形式來指定一個不同的轉換器。

        變量部分將被作爲關鍵字參數傳入視圖函數。

        可用的轉換器如下所示:

        ========= =======================================
        int       接受整型
        float     類似int,但是接受浮點數(floating point)
        path      類似默認值,但接受斜線
        ========= =======================================

        下面是一些示例:

            @app.route('/')
            def index():
                pass

            @app.route('/<username>')
            def show_user(username):
                pass

            @app.route('/post/<int:post_id>')
            def show_post(post_id):
                pass

        一個重要的細節是留意Flask是如何處理斜線的。爲了讓每一個URL獨一無二,
        下面的規則被應用:

        1. 如果一個規則以一個斜線結尾而用戶請求的地址不包含斜線,那麼該用戶
        會被重定向到相同的頁面並附加一個結尾斜線。
        2. 如果一個規則沒有以斜線結尾而用戶請求的頁面包含了一個結尾斜線,
        會拋出一個404錯誤。
        
        這和Web服務器處理靜態文件的方式相一致。這也可以讓你安全的使用相對鏈接目標。

        這個route裝飾器也接受一系列參數:

        :param rule: 字符串形式的URL規則
        :param methods: 一個方法列表,可用的值限定爲(GET、POST等)。默認一個
                        規則僅監聽GET(以及隱式的HEAD)
        :param subdomain: 當子域名匹配使用時,爲規則指定子域。
        :param strict_slashes: 可以用來爲這個規則關閉嚴格的斜線設置,見上。
        :param options: 轉發到底層的werkzeug.routing.Rule對象的其他選項。
        """
        def decorator(f):
            self.add_url_rule(rule, f.__name__, **options)
            self.view_functions[f.__name__] = f
            return f
        return decorator

    def errorhandler(self, code):
        """一個用於爲給定的錯誤碼註冊函數的裝飾器。示例:

            @app.errorhandler(404)
            def page_not_found():
                return 'This page does not exist', 404

        你也可以不使用errorhandler註冊一個函數作爲錯誤處理器。下面的例子同上:

            def page_not_found():
                return 'This page does not exist', 404
            app.error_handlers[404] = page_not_found

        :param code: 對應處理器的整型類型的錯誤代碼。
        """
        def decorator(f):
            self.error_handlers[code] = f
            return f
        return decorator

    def before_request(self, f):
        """註冊一個函數,則每一個請求處理前調用。"""
        self.before_request_funcs.append(f)
        return f

    def after_request(self, f):
        """註冊一個函數,在每一個請求處理後調用。"""
        self.after_request_funcs.append(f)
        return f

    def context_processor(self, f):
        """註冊一個模板上下文處理函數。"""
        self.template_context_processors.append(f)
        return f

    def match_request(self):
        """基於URL映射(map)匹配當前請求。如果匹配成功,同時也存儲端點和
        視圖參數,否則存儲異常。
        """
        rv = _request_ctx_stack.top.url_adapter.match()
        request.endpoint, request.view_args = rv
        return rv

    def dispatch_request(self):
        """附註請求分發工作。匹配URL,返回視圖函數或錯誤器的返回值。這個返回值
        不一定得是響應對象。爲了將返回值返回值轉換成合適的想要對象,
        調用make_response。
        """
        try:
            endpoint, values = self.match_request()
            return self.view_functions[endpoint](**values)
        except HTTPException, e:
            handler = self.error_handlers.get(e.code)
            if handler is None:
                return e
            return handler(e)
        except Exception, e:
            handler = self.error_handlers.get(500)
            if self.debug or handler is None:
                raise
            return handler(e)

    def make_response(self, rv):
        """將視圖函數的返回值轉換成一個真正的響應對象,即response_class實例。

        rv允許的類型如下所示:

        ======================= ===============================================
        response_class          這個對象將被直接返回
        str                     使用這個字符串作爲主體創建一個請求對象
        unicode                 將這個字符串進行utf-8編碼後作爲主體創建一個請求對象
        tuple                   使用這個元組的內容作爲參數創建一個請求對象
        a WSGI function         這個函數將作爲WSGI程序調用並緩存爲響應對象
        ======================= ===============================================

        :param rv: 視圖函數返回值
        """
        if isinstance(rv, self.response_class):
            return rv
        if isinstance(rv, basestring):
            return self.response_class(rv)
        if isinstance(rv, tuple):
            return self.response_class(*rv)
        return self.response_class.force_type(rv, request.environ)

    def preprocess_request(self):
        """在實際的請求分發之前調用,而且將會調用每一個使用before_request
        裝飾的函數。如果其中某一個函數返回一個值,這個值將會作爲視圖返回值
        處理並停止進一步的請求處理。
        """
        for func in self.before_request_funcs:
            rv = func()
            if rv is not None:
                return rv

    def process_response(self, response):
        """爲了在發送給WSGI服務器前修改響應對象,可以重寫這個方法。 默認
        這會調用所有使用after_request裝飾的函數。

        :param response: 一個response_class對象。
        :return: 一個新的響應對象或原對象,必須是response_class實例。
        """
        session = _request_ctx_stack.top.session
        if session is not None:
            self.save_session(session, response)
        for handler in self.after_request_funcs:
            response = handler(response)
        return response

    def wsgi_app(self, environ, start_response):
        """實際的WSGI程序。它沒有通過__call__實現,因此可以附加中間件:
        
            app.wsgi_app = MyMiddleware(app.wsgi_app)

        :param environ: 一個WSGI環境。
        :param start_response: 一個接受狀態碼的可調用對象,一個包含首部
                               的列表以及一個可選的用於啓動響應的異常上下文。
        """
        with self.request_context(environ):
            rv = self.preprocess_request()
            if rv is None:
                rv = self.dispatch_request()
            response = self.make_response(rv)
            response = self.process_response(response)
            return response(environ, start_response)

    def request_context(self, environ):
        """從給定的環境創建一個請求上下文,並將其綁定到當前上下文。這必須搭配with
        語句使用,因爲請求僅綁定在with塊中的當前上下文裏。

        用法示例:
            
            with app.request_context(environ):
                do_something_with(request)

        :params environ: 一個WSGI環境。
        """
        return _RequestContext(self, environ)

    def test_request_context(self, *args, **kwargs):
        """從給定的值創建一個WSGI環境(更多信息請參見werkzeug.create_environ,
        這個函數接受相同的參數)。
        """
        return self.request_context(create_environ(*args, **kwargs))

    def __call__(self, environ, start_response):
        """wsgi_app的快捷方式。"""
        return self.wsgi_app(environ, start_response)


# 本地上下文
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)


腳本可以按照從頭到尾的順序閱讀,0.1版本中有些用法和當前版本有很大出入,不能把上述代碼應用到開發中。


好書必須分享,做一回自來水

推薦看作者的入門書:

《Flask Web開發實戰 入門、進階與原理解析》

瞭解一下:http://helloflask.com/book/

image.png


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章