flask 源碼 0.1 版本

閱讀flask源碼,能夠先從flask最先的版本開始閱讀
css

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

源碼文檔來自:python

(英文版)git

https://github.com/pallets/flaskgithub

(greyli翻譯)sql

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


英文版:數據庫

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


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 <withlihui@gmail.com>
    :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

相關文章
相關標籤/搜索