flask源码阅读系列一config模块

import_string

先来分析一下这个动态导入函数:werkzeug.utils.import_string

def import_string(import_name, silent=False):
    """基于字符串动态导入对象。字符串有两种形式写法用点来分割对象名(``xml.sax.saxutils.escape``)和用冒号来分割对象名
(``xml.sax.saxutils:escape``).
silent决定发生异常时是返回None还是抛出一些异常(安静,挺形象的。)
    """

    import_name = str(import_name).replace(':', '.')
    try:
        try:
            #首先假设import_name是一个包名或者模块名(非对象名)。
            __import__(import_name)
        except ImportError: 
            if '.' not in import_name: #没有点说明即使我们假设错误,也没法继续尝试。故而立即抛出异常
                raise
        else:
        #如果导入正确,那么可以从sys.modules字典里面获取已经导入的模块或包.
        #为什么要通过sys.modules[import_name]返回而不是直接return  __import__(import_name)呢?因为__import__在没有提供fromlist参数时,返回值策略为返回第一个点之前的顶级包对象,比如'os.path'返回的是os而不是os.path.
            return sys.modules[import_name]
        #如果import_name不是包名或模块名,而是具体的对象、函数名。
        module_name, obj_name = import_name.rsplit('.', 1)
        try:
        #相当于from module_name import obj_name
            module = __import__(module_name, None, None, [obj_name])
        except ImportError:
            #如果支持的模块没有被父模块设置好的情形中会导入失败,这时需要先导入父模块(或包)。典型的例子就是logging与logging.config。
            module = import_string(module_name)

        try:
        #通过getarr从模块中返回具体对象
            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]) 

flask.config模块

import os
import types
import errno

from werkzeug.utils import import_string
from ._compat import string_types, iteritems
from . import json

class Config(dict):
    """config类除了正常的dict职责外,还具有从py文件(模块)、对象或字典填充值的能力。
        from_envvar会调用from_pyfile会调用from_object,另还有from_json会调用from_mapping
       还有一个便利的get_namespace方法。
    """

    def __init__(self, root_path, defaults=None): # root_path查找py文件的基路径。
        dict.__init__(self, defaults or {})
        self.root_path = root_path
    def from_envvar(self, variable_name, silent=False):
        """variable_name是一个环境变量名,指向一个py文件。返回bool值,是否加载填充成功。
        """
        rv = os.environ.get(variable_name)
        #...省略部分校验逻辑...
        #这里调用from_pyfile
        return self.from_pyfile(rv, silent=silent)

    def from_pyfile(self, filename, silent=False):
        filename = os.path.join(self.root_path, filename)
        #利用标准库types来动态创建一个模块对象,名为config,文件属性(__file__)指向设置为前面的filename
        d = types.ModuleType('config')
        d.__file__ = filename
        try:
            with open(filename) as config_file:
                #exec,compile都是内置函数。
                #exec(object[, globals[, locals]]) 参数object可以为str或者字节码对象,返回None。这里可以理解为解释字节码,并将字节码执行过程中的全局变量存入d.__dict__字典中。
                #compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
                #compile用于动态编译文件为字节码对象,其中'exec'为编译模式,代表整个文件(多行)编译,其余模式还有'eval'单行语句编译,'single'单行交互式语句编译。
                exec(compile(config_file.read(), filename, 'exec'), d.__dict__)
        except IOError as e:
            ...略pass...
        #这里调用from_object,from_object才是关键。
        self.from_object(d)
        return True

    def from_object(self, obj):
        """参数obj可以是str(需要被导入的对象)或者一个对象。它只允许模块或对象中的大写属性填充进来,小写的忽略。
        你可以这样来使用本方法
            app.config.from_object('yourapplication.default_config')或者
            from yourapplication import default_config
            app.config.from_object(default_config)
        """
        if isinstance(obj, string_types):
            obj = import_string(obj)
        for key in dir(obj):
            if key.isupper(): #只允许模块或对象中的大写属性填充进来,小写的忽略。
                self[key] = getattr(obj, key)

    def from_json(self, filename, silent=False):
        """filename是json文件名。这是flask0.11添加的方法。
        """
        filename = os.path.join(self.root_path, filename)
        try:
            with open(filename) as json_file:
                obj = json.loads(json_file.read()) #注意,如果不知道json.loads的type参数,那么默认加载为字典。即obj是一个字典
        except IOError as e:
            ...略pass...
        return self.from_mapping(obj) #这里调用from_mapping

    def from_mapping(self, *mapping, **kwargs):
        """行为类似于dict的update方法,只不过忽略小写属性.flask0.11添加的方法
        """
        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

    def get_namespace(self, namespace, lowercase=True, trim_namespace=True):
        """返回一个字典,这个字典包含该命名空间的items。这个命名空间是以_作为分隔标识的.比如
            app.config['IMAGE_STORE_TYPE'] = 'fs'
            app.config['IMAGE_STORE_PATH'] = '/var/app/images'
            app.config['IMAGE_STORE_BASE_URL'] = 'http://img.website.com'
            image_store_config = app.config.get_namespace('IMAGE_STORE_')
        结果 `image_store_config` 会是这样::

            {
                'type': 'fs',
                'path': '/var/app/images',
                'base_url': 'http://img.website.com'
            }
        当需要利用这个进行传参时是比较方便的。
        trim_namespace参数为True时,行为就是上面所述,为False时返回的key会包含namespace前缀。
        """
        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

    def __repr__(self):
        return '<%s %s>' % (self.__class__.__name__, dict.__repr__(self))
        
config模块阅读配置到此结束,下一篇主题待定。
    原文作者:xbynet
    原文地址: https://segmentfault.com/a/1190000008106016
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞