原始碼解析Flask的配置檔案

任平生78發表於2018-04-07

在flask裡,我們常在主檔案中定義某些配置,比如:

app.debug = True
app.secret_key = `helloworld!!`

實際上,flask中預設可以進行可選的配置項有很多。

如果在開發的過程中,把所有需要的配置項都定義在主檔案中,就會造成整個程式的目錄結構不合理,
如果需要重寫的flask配置項很多的時候,就可以把配置項用別的方式進行定義,然後匯入使用

flask的配置檔案是一個flask.config.Config物件

匯入Config物件,可以發現Config物件繼承字典,

Config預設的配置有:

default_config = ImmutableDict({
    `DEBUG`:                                get_debug_flag(default=False),
    `TESTING`:                              False,
    `PROPAGATE_EXCEPTIONS`:                 None,
    `PRESERVE_CONTEXT_ON_EXCEPTION`:        None,
    `SECRET_KEY`:                           None,
    `PERMANENT_SESSION_LIFETIME`:           timedelta(days=31),
    `USE_X_SENDFILE`:                       False,
    `LOGGER_NAME`:                          None,
    `LOGGER_HANDLER_POLICY`:               `always`,
    `SERVER_NAME`:                          None,
    `APPLICATION_ROOT`:                     None,
    `SESSION_COOKIE_NAME`:                  `session`,
    `SESSION_COOKIE_DOMAIN`:                None,
    `SESSION_COOKIE_PATH`:                  None,
    `SESSION_COOKIE_HTTPONLY`:              True,
    `SESSION_COOKIE_SECURE`:                False,
    `SESSION_REFRESH_EACH_REQUEST`:         True,
    `MAX_CONTENT_LENGTH`:                   None,
    `SEND_FILE_MAX_AGE_DEFAULT`:            timedelta(hours=12),
    `TRAP_BAD_REQUEST_ERRORS`:              False,
    `TRAP_HTTP_EXCEPTIONS`:                 False,
    `EXPLAIN_TEMPLATE_LOADING`:             False,
    `PREFERRED_URL_SCHEME`:                 `http`,
    `JSON_AS_ASCII`:                        True,
    `JSON_SORT_KEYS`:                       True,
    `JSONIFY_PRETTYPRINT_REGULAR`:          True,
    `JSONIFY_MIMETYPE`:                     `application/json`,
    `TEMPLATES_AUTO_RELOAD`:                None,
})

通過檢視Config物件的原始碼,可以知道flask的配置可以有以下幾種方式

1.在主檔案中定義(通常使用的方式)

app.debug = True
app.secret_key = `helloworld!!`

由於Config物件繼承了dict的方法和屬性,所以還可以使用app.config.update(配置項)的方式匯入配置項

2.從環境變數中匯入配置項

匯入配置項的方式:

app.config.from_envvar("環境變數名稱")

from_envvar方法的原始碼:

def from_envvar(self, variable_name, silent=False):
    
    rv = os.environ.get(variable_name)
    if not rv:
        if silent:
            return False
        raise RuntimeError(`The environment variable %r is not set `
                           `and as such configuration could not be `
                           `loaded.  Set this variable and make it `
                           `point to a configuration file` %
                           variable_name)
    return self.from_pyfile(rv, silent=silent)

可以看到,從環境變數中匯入配置項的方法,就是從環境變數中找到並讀取對應的py檔名稱,然後內部呼叫from_pyfile方法處理讀取到的內容得到配置

3.從python檔案中匯入

從python檔案中獲取配置項的方式:

app.config.from_pyfile("python檔名稱")

例如,建立一個名為setting.py的檔案

setting.py檔案的內容為:

DEBUG=True

然後使用app.config.from_pyfile("setting.py")的方式匯入配置項

from_pyfile方法的原始碼:

def from_pyfile(self, filename, silent=False):

    filename = os.path.join(self.root_path, filename)
    d = types.ModuleType(`config`)
    d.__file__ = filename
    try:
        with open(filename, mode=`rb`) as config_file:
            exec(compile(config_file.read(), filename, `exec`), d.__dict__)
    except IOError as e:
        if silent and e.errno in (errno.ENOENT, errno.EISDIR):
            return False
        e.strerror = `Unable to load configuration file (%s)` % e.strerror
        raise
    self.from_object(d)
    return True

從py檔案中匯入配置項的過程中,讀取引數中的python檔案的內容,進行編譯後exec方法執行,就得到所需要的配置項

需要注意的是:

python檔案可以是絕對路徑或者相對路徑,如果是相對路徑,則py檔案必須放在root_path目錄下,

4.從物件中匯入配置項

from_object方法的原始碼:

def from_object(self, obj):

    if isinstance(obj, string_types):
        obj = import_string(obj)
    for key in dir(obj):
        if key.isupper():
            self[key] = getattr(obj, key)

從物件中匯入配置項的過程中,首先判斷所傳入的物件名是否是字串,然後呼叫import_string方法處理字串形式的物件名

import_string方法的原始碼:

def import_string(import_name, silent=False):

    import_name = str(import_name).replace(`:`, `.`)
    try:
        try:
            __import__(import_name)
        except ImportError:
            if `.` not in import_name:
                raise
        else:
            return sys.modules[import_name]

        module_name, obj_name = import_name.rsplit(`.`, 1)
        try:
            module = __import__(module_name, None, None, [obj_name])
        except ImportError:
            module = import_string(module_name)

        try:
            return getattr(module, obj_name)
        except AttributeError as e:
            raise ImportError(e)

    except ImportError as e:
        if not silent:
            reraise(
                ImportStringError,
                ImportStringError(import_name, e),
                sys.exc_info()[2])

可以看到,import_string方法,實際上是對字串形式的物件名執行rsplit方法,得到模組名和物件名

在模組可以被正常匯入之前,不停執行import_string方法,最後執行getattr方法從模組中獲取物件名

5.from_json:從json字串中獲取配置項

from_json方法的原始碼:

def from_json(self, filename, silent=False):
    
    filename = os.path.join(self.root_path, filename)

    try:
        with open(filename) as json_file:
            obj = json.loads(json_file.read())
    except IOError as e:
        if silent and e.errno in (errno.ENOENT, errno.EISDIR):
            return False
        e.strerror = `Unable to load configuration file (%s)` % e.strerror
        raise
    return self.from_mapping(obj)

從json檔案中獲取配置項,實際上就是對json檔案執行json.loads方法,得到物件

然後內部呼叫from_mapping方法處理所得到的物件

6.from_mapping:從dict字典中獲取配置項

from_mapping方法的原始碼:

def from_mapping(self, *mapping, **kwargs):

    mappings = []
    if len(mapping) == 1:
        if hasattr(mapping[0], `items`):
            mappings.append(mapping[0].items())
        else:
            mappings.append(mapping[0])
    elif len(mapping) > 1:
        raise TypeError(
            `expected at most 1 positional argument, got %d` % len(mapping)
        )
    mappings.append(kwargs.items())
    for mapping in mappings:
        for (key, value) in mapping:
            if key.isupper():
                self[key] = value
    return True

把引數字典中的所有鍵值對新增到列表串,迴圈遍歷列表,讀取列表中每個元素的鍵和值

如果鍵為大寫,則key為配置選項,value為配置選項的值

7.get_namespace:從名稱空間中獲取配置選項

get_namespace原始碼:

def get_namespace(self, namespace, lowercase=True, trim_namespace=True):

    rv = {}
    for k, v in iteritems(self):
        if not k.startswith(namespace):
            continue
        if trim_namespace:
            key = k[len(namespace):]
        else:
            key = k
        if lowercase:
            key = key.lower()
        rv[key] = v
    return rv

get_namespace方法,是從指定的名稱空間或字首中進行匹配,返回包含配置項的子集的字典

迭代當前物件,獲取key和v,把key轉換為小寫格式,然後把key和v包含在一個字典中


相關文章