示例#1
0
文件: conf.py 项目: neithere/tool
def load(path, format=None):
    """
    Expects a filename, returns a dictionary. Raises ConfigurationError if
    the file could not be read or parsed.

    :param path:
        path to the file.
    :param format:
        format in which the configuration dictionary in serialized in the file
        (one of: "yaml", "json").

    """
    if not os.path.exists(path):
        raise ConfigurationError('File "%s" does not exist' % path)

    # guess file format
    if not format:
        for known_format in FORMATS:
            if path.endswith('.%s' % known_format):
                format = known_format
                break
        else:
            raise ConfigurationError('Could not guess format for "%s"' % path)
    assert format in FORMATS, 'unknown format %s' % format

    # deserialize file contents to a Python dictionary
    try:
        f = open(path)
    except IOError as e:
        raise ConfigurationError('Could not open "%s": %s' % (path, e))
    data = f.read()
    try:
        loader = import_attribute(FORMATS[format])
    except ImportError as e:
        raise ConfigurationError('Could not import "%s" format loader: %s'
                                 % (format, e))
    try:
        conf = loader(data)
    except Exception as e:
        raise ConfigurationError('Could not deserialize config data: %s' % e)

    if not isinstance(conf, dict):
        raise ConfigurationError('Deserialized config must be a dict, got "%s"'
                                 % conf)
    return conf
示例#2
0
    def _load_extensions(self):
        # configured extensions (instances) are indexed by full dotted path
        # (including class name). It is also possible to access them by feature
        # name: see Application.get_feature().

        logger.debug('Loading extensions...')

        _extensions = {}
        _features = {}

        # collect extensions, make sure they can be imported and group them by
        # identity. The identity is declared by the extension class. It is usually
        # the dotted path to the extension module but can be otherwse if the
        # extension implements a named role (e.g. "storage" or "templating").
        if not 'extensions' in self.settings:
            import warnings
            warnings.warn('No extensions configured. Application is unusable.')

        for path in self.settings.get('extensions', []):
            assert isinstance(path, basestring), (
                'cannot load extension by module path: expected a string, '
                'got {0}'.format(repr(bundle)))
            logger.debug('Loading (importing) extension {0}'.format(path))
            #
            # NOTE: the "smart" stuff is commented out because in most cases
            # this hides the valuable call stack; moreover, this happens on
            # start so wrapping is really unnecessary.
            #
            #try:
            #    cls = import_attribute(path)
            #except (ImportError, AttributeError) as e:
            #    raise ConfigurationError(
            #        'Could not load extension "{0}": {1}'.format(path, e))
            #
            cls = import_attribute(path)
            conf = self.settings['extensions'][path]

            _extensions[path] = cls, conf
            if getattr(cls, 'features', None):
                # XXX here "features" is a verb, not a noun; may be misleading
#                for feature in cls.features:
                if not isinstance(cls.features, basestring):
                    raise ConfigurationError(
                        'Extension should supply its feature name as string; '
                        '{path} defines {cls.features}'.format(**locals()))
                assert cls.features not in _features, (
                    '{feature} must be unique'.format(**locals()))
                _features[cls.features] = path

        # now actually initialize the extensions and save them to the application
        # manager instance. This involves checking dependencies. They are
        # listed as dotted paths ("foo.bar") or features ("{quux}"). The
        # features must be dereferenced to dotted paths. We can only do it
        # after we have an imported class that declares itself an
        # implementation of given feature (i.e. "class MyExt: features='foo'").
        # That's why we are messing with two loading stages.

        self._extensions = {}
        self._features = _features

        stacked = {}
        loaded = {}

        # TODO: refactor (too complex, must be easily readable)
        def load_extension(path):
            if path in stacked:
                # raise a helpful exception to track down a circular dependency
                classes = [_extensions[x][0] for x in stacked]
                related = [p for p in classes
                              if p.requires and path in [r.format(**_features) for r in p.requires]]
                strings = ['.'.join([p.__module__,p.__name__])
                                 for p in related]
                raise RuntimeError('Cannot load extension "{0}": circular '
                                   'dependency with {1}'.format(path,
                                                                strings))

            if path in loaded:
                # this happens if the plugin has already been loaded as a
                # dependency of another plugin
                logger.debug('Already loaded: {0}'.format(path))
                return

            cls, conf = _extensions[path]

            assert path not in self._extensions, (
                'Cannot load extension {0}: path "{1}" is already loaded as '
                '{2}.'.format(cls, path, self._extensions[path]))

            stacked[path] = True  # to prevent circular dependencies

            # load dependencies
            if getattr(cls, 'requires', None):
                assert isinstance(cls.requires, (tuple, list)), (
                    '{0}.{1}.requires must be a list or tuple'.format(
                        cls.__module__, cls.__name__))
                for req_string in cls.requires:
                    # TODO: document this behaviour, i.e. "{templating}" -> "tool.ext.jinja"
                    try:
                        requirement = req_string.format(**_features)
                    except KeyError as e:
                        raise ConfigurationError(
                            'Unknown feature "{0}". Expected one of '
                            '{1}'.format(e, list(_features)))

                    logger.debug('Dependency: {0} requires {1}'.format(path, requirement))
                    if path == requirement:
                        raise ConfigurationError('{0} requires itself.'.format(path))
                    if requirement not in _extensions:
                        raise ConfigurationError(
                            'Plugin {0}.{1} requires extension "{2}" which is '
                            'not configured. These are configured: '
                            '{3}.'.format(cls.__module__, cls.__name__,
                                          requirement, _extensions.keys()))
                    load_extension(requirement)  # recursion

            # initialize and register the extension
            extension = cls(self, conf)
            self._extensions[path] = extension

            loaded[path] = True
            stacked.pop(path)

        # load each extension with recursive dependencies
        for path in _extensions:
            load_extension(path)